You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by jo...@apache.org on 2013/08/14 20:56:31 UTC

[01/25] git commit: [#6458] standardize importer label

Updated Branches:
  refs/heads/cj/6464 243bfb7f0 -> 778cece02 (forced update)


[#6458] standardize importer label


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/3cd01607
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/3cd01607
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/3cd01607

Branch: refs/heads/cj/6464
Commit: 3cd01607850993bf2090108ca2ed8eb150b847c0
Parents: ee1c591
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Thu Aug 8 21:25:40 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Aug 8 21:25:45 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/code.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/3cd01607/ForgeImporters/forgeimporters/google/code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/code.py b/ForgeImporters/forgeimporters/google/code.py
index c88d42a..2f8d19f 100644
--- a/ForgeImporters/forgeimporters/google/code.py
+++ b/ForgeImporters/forgeimporters/google/code.py
@@ -97,7 +97,7 @@ class GoogleRepoImporter(ToolImporter):
     target_app = TARGET_APPS
     source = 'Google Code'
     controller = GoogleRepoImportController
-    tool_label = 'Google Code Source Importer'
+    tool_label = 'Source Code'
     tool_description = 'Import your SVN, Git, or Hg repo from Google Code'
 
     def import_tool(self, project, user, project_name=None, mount_point=None,


[14/25] git commit: [#6553] fix tests corresponding to taskd error handling

Posted by jo...@apache.org.
[#6553] fix tests corresponding to taskd error handling


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/6657808d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/6657808d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/6657808d

Branch: refs/heads/cj/6464
Commit: 6657808db549aa90dfff9e0ca6c45f8b2bfaddad
Parents: c700603
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Wed Aug 14 13:32:53 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 14 13:32:53 2013 +0000

----------------------------------------------------------------------
 Allura/allura/tests/test_commands.py |  3 +--
 Allura/allura/tests/test_tasks.py    | 12 +++++++++++-
 2 files changed, 12 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6657808d/Allura/allura/tests/test_commands.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index 0cecfbf..09dc3b6 100644
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -344,8 +344,7 @@ class TestBackgroundCommand(object):
     def test_invalid_args(self):
         M.MonQTask.query.remove()
         show_models.ReindexCommand.post('--invalid-option')
-        with td.raises(Exception):
-            M.MonQTask.run_ready()
+        M.MonQTask.run_ready()
         task = M.MonQTask.query.get(task_name=self.task_name)
         assert_equal(task.state, 'error')
         assert_in('Error parsing args', task.result)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6657808d/Allura/allura/tests/test_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index c85ce50..c72d253 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -21,6 +21,7 @@ import operator
 import sys
 import unittest
 from base64 import b64encode
+import logging
 
 import mock
 from pylons import tmpl_context as c, app_globals as g
@@ -28,6 +29,7 @@ from datadiff.tools import assert_equal
 from nose.tools import assert_in
 from ming.orm import FieldProperty, Mapper
 from ming.orm import ThreadLocalORMSession
+from testfixtures import LogCapture
 
 from alluratest.controller import setup_basic_test, setup_global_objects
 
@@ -74,7 +76,15 @@ class TestEventTasks(unittest.TestCase):
         setup_basic_test()
         setup_global_objects()
         t = raise_exc.post()
-        self.assertRaises(CompoundError, t)
+        with LogCapture(level=logging.ERROR) as l:
+            t()
+        # l.check() would be nice, but string is too detailed to check
+        assert_equal(l.records[0].name, 'allura.model.monq_model')
+        msg = l.records[0].getMessage()
+        assert_in("AssertionError('assert 0',)", msg)
+        assert_in("AssertionError('assert 5',)", msg)
+        assert_in(' on job <MonQTask ', msg)
+        assert_in(' (error) P:10 allura.tests.test_tasks.raise_exc ', msg)
         for x in range(10):
             assert ('assert %d' % x) in t.result
 


[12/25] git commit: [#4818] sort entry points for reliable ordering

Posted by jo...@apache.org.
[#4818] sort entry points for reliable ordering


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/c7006032
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/c7006032
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/c7006032

Branch: refs/heads/cj/6464
Commit: c70060322432813497d338c502d734eafe808bd4
Parents: c0dbb7f
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Fri Aug 9 21:47:00 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Aug 14 13:12:13 2013 +0000

----------------------------------------------------------------------
 Allura/allura/ext/admin/admin_main.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c7006032/Allura/allura/ext/admin/admin_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index 98a34c8..06aaec1 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -140,7 +140,8 @@ class AdminApp(Application):
             links.append(None)
             links.append(SitemapEntry('Help', nbhd_admin_url+ 'help/'))
 
-        for name, admin_extension in g.entry_points['admin'].iteritems():
+        for ep_name in sorted(g.entry_points['admin'].keys()):
+            admin_extension = g.entry_points['admin'][ep_name]
             admin_extension().update_project_sidebar_menu(links)
 
         return links
@@ -156,7 +157,8 @@ class AdminExtensionLookup(object):
 
     @expose()
     def _lookup(self, name, *remainder):
-        for ext_name, admin_extension in g.entry_points['admin'].iteritems():
+        for ep_name in sorted(g.entry_points['admin'].keys()):
+            admin_extension = g.entry_points['admin'][ep_name]
             controller = admin_extension().project_admin_controllers.get(name)
             if controller:
                 return controller(), remainder


[22/25] git commit: [#6464] Added tests for GC tracker attachments and comments

Posted by jo...@apache.org.
[#6464] Added tests for GC tracker attachments and comments

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/e4f4e090
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/e4f4e090
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/e4f4e090

Branch: refs/heads/cj/6464
Commit: e4f4e090c678dabde8e3494f3390474ca1d0f093
Parents: 50faffb
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri Aug 9 16:25:01 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 14 15:41:13 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/google/__init__.py           | 10 +++-
 .../tests/data/google/test-issue.html           | 38 +++++++++++++
 .../tests/google/test_extractor.py              | 60 ++++++++++++++++++++
 .../forgeimporters/tests/google/test_tracker.py |  4 +-
 4 files changed, 107 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4f4e090/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 9f5324a..37f70db 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -192,8 +192,12 @@ class GoogleCodeProjectExtractor(object):
         return self.page.find(id='hc0').find('span', 'date').get('title')
 
     def get_issue_mod_date(self):
-        last_update = Comment(self.page.findAll('div', 'issuecomment')[-1])
-        return last_update.created_date
+        comments = self.page.findAll('div', 'issuecomment')
+        if comments:
+            last_update = Comment(comments[-1])
+            return last_update.created_date
+        else:
+            return self.get_issue_created_date()
 
     def get_issue_creator(self):
         a = self.page.find(id='hc0').find('a', 'userlink')
@@ -237,7 +241,7 @@ class Comment(object):
     def __init__(self, tag):
         self.author = UserLink(tag.find('span', 'author').find('a', 'userlink'))
         self.created_date = tag.find('span', 'date').get('title')
-        self.body = _as_text(tag.find('pre'))
+        self.body = _as_text(tag.find('pre')).strip()
         self._get_updates(tag)
         self._get_attachments(tag)
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4f4e090/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/data/google/test-issue.html b/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
index 04667ac..dbef54e 100644
--- a/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
+++ b/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
@@ -282,6 +282,44 @@ That's all
 
 
 </pre>
+<div class="attachments">
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000000&amp;name=at1.txt&amp;token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at1.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=7&amp;aid=70000000&amp;name=at1.txt&amp;token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000000&amp;name=at1.txt&amp;token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255">Download</a>
+</td>
+</tr>
+</table>
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000001&amp;name=at2.txt&amp;token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at2.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=7&amp;aid=70000001&amp;name=at2.txt&amp;token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000001&amp;name=at2.txt&amp;token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255">Download</a>
+</td>
+</tr>
+</table>
+</div>
 </div>
 <div class="cursor_off vt issuecomment" id="hc1">
 <div style="float:right; margin-right:.3em; text-align:right">

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4f4e090/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index f6e2b2c..78176f8 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -139,6 +139,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
         self.assertEqual(gpe.get_issue_status(), '')
         self.assertEqual(gpe.get_issue_attachments(), [])
         self.assertEqual(list(gpe.iter_comments()), [])
+        self.assertEqual(gpe.get_issue_mod_date(), 'Thu Aug  8 14:56:23 2013')
 
     def test_get_issue_basic_fields(self):
         test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
@@ -188,3 +189,62 @@ class TestGoogleCodeProjectExtractor(TestCase):
                 'OpSys-Windows',
                 'OpSys-OSX',
             ])
+
+    def test_get_issue_attachments(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        attachments = gpe.get_issue_attachments()
+        self.assertEqual(len(attachments), 2)
+        self.assertEqual(attachments[0].filename, 'at1.txt')
+        self.assertEqual(attachments[0].url, 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000000&name=at1.txt&token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255')
+        self.assertIsNone(attachments[0].type)
+        self.assertEqual(attachments[1].filename, 'at2.txt')
+        self.assertEqual(attachments[1].url, 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000001&name=at2.txt&token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255')
+        self.assertIsNone(attachments[1].type)
+
+    def test_iter_comments(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        comments = list(gpe.iter_comments())
+        self.assertEqual(len(comments), 4)
+        expected = [
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:35:15 2013',
+                    'body': 'Test *comment* is a comment',
+                    'updates': {'Status:': 'Started', 'Labels:': '-OpSys-Linux OpSys-Windows'},
+                    'attachments': ['at2.txt'],
+                },
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:35:34 2013',
+                    'body': 'Another comment',
+                    'updates': {},
+                    'attachments': [],
+                },
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:36:39 2013',
+                    'body': 'Last comment',
+                    'updates': {},
+                    'attachments': ['at4.txt', 'at1.txt'],
+                },
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:36:57 2013',
+                    'body': 'Oh, I forgot one',
+                    'updates': {'Labels:': 'OpSys-OSX'},
+                    'attachments': [],
+                },
+            ]
+        for actual, expected in zip(comments, expected):
+            self.assertEqual(actual.author.name, expected['author.name'])
+            self.assertEqual(actual.author.link, expected['author.link'])
+            self.assertEqual(actual.created_date, expected['created_date'])
+            self.assertEqual(actual.body, expected['body'])
+            self.assertEqual(actual.updates, expected['updates'])
+            self.assertEqual([a.filename for a in actual.attachments], expected['attachments'])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4f4e090/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index 62493bd..a1f0a28 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -116,7 +116,7 @@ class TestTrackerImporter(TestCase):
             return u
         issue = mock.Mock(
                 get_issue_summary=lambda:'summary',
-                get_issue_description=lambda:'description',
+                get_issue_description=lambda:'my *description* fool',
                 get_issue_status=lambda:'status',
                 get_issue_created_date=lambda:'created_date',
                 get_issue_mod_date=lambda:'mod_date',
@@ -128,7 +128,7 @@ class TestTrackerImporter(TestCase):
             dt.strptime.side_effect = lambda s,f: s
             importer.process_fields(ticket, issue)
             self.assertEqual(ticket.summary, 'summary')
-            self.assertEqual(ticket.description, '*Originally created by:* [cname](clink)\n*Originally owned by:* [oname](olink)\n\ndescription')
+            self.assertEqual(ticket.description, '*Originally created by:* [cname](clink)\n*Originally owned by:* [oname](olink)\n\nmy \*description\* fool')
             self.assertEqual(ticket.status, 'status')
             self.assertEqual(ticket.created_date, 'created_date')
             self.assertEqual(ticket.mod_date, 'mod_date')


[10/25] git commit: [#4818] add documentation for extensions

Posted by jo...@apache.org.
[#4818] add documentation for extensions


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/df942cb2
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/df942cb2
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/df942cb2

Branch: refs/heads/cj/6464
Commit: df942cb262f9b541dd6235420e3a39c1398e6693
Parents: 5f836ba
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Fri Aug 9 19:41:28 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Aug 14 13:12:13 2013 +0000

----------------------------------------------------------------------
 Allura/allura/app.py               |  2 +-
 Allura/allura/lib/plugin.py        | 10 +++---
 Allura/docs/api/app.rst            | 13 +-------
 Allura/docs/api/lib/plugin.rst     | 24 ++++++++++++++
 Allura/docs/api/lib/spam.rst       | 24 ++++++++++++++
 Allura/docs/extending.rst          | 55 +++++++++++++++++++++++++++++++++
 Allura/docs/guides/message_bus.rst |  4 ++-
 Allura/docs/index.rst              |  3 +-
 8 files changed, 115 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/df942cb2/Allura/allura/app.py
----------------------------------------------------------------------
diff --git a/Allura/allura/app.py b/Allura/allura/app.py
index c1d2517..5ac11f2 100644
--- a/Allura/allura/app.py
+++ b/Allura/allura/app.py
@@ -164,7 +164,7 @@ class Application(object):
     The base Allura pluggable application
 
     After extending this, expose the app by adding an entry point in your
-    setup.py:
+    setup.py::
 
         [allura]
         myapp = foo.bar.baz:MyAppClass

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/df942cb2/Allura/allura/lib/plugin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index f972c26..f600363 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -56,12 +56,12 @@ class AuthenticationProvider(object):
     '''
     An interface to provide authentication services for Allura.
 
-    To use a new provider, expose an entry point in setup.py:
+    To use a new provider, expose an entry point in setup.py::
 
         [allura.auth]
         myprovider = foo.bar:MyAuthProvider
 
-    Then in your .ini file, set auth.method=myprovider
+    Then in your .ini file, set ``auth.method=myprovider``
     '''
 
     def __init__(self, request):
@@ -346,7 +346,7 @@ class ProjectRegistrationProvider(object):
     and the default.  Extend this class with your own if you need to add more
     functionality.
 
-    To use a new provider, expose an entry point in setup.py:
+    To use a new provider, expose an entry point in setup.py::
 
         [allura.project_registration]
         myprovider = foo.bar:MyAuthProvider
@@ -625,7 +625,7 @@ class ThemeProvider(object):
     and the default.  Extend this class with your own if you need to add more
     functionality.
 
-    To use a new provider, expose an entry point in setup.py:
+    To use a new provider, expose an entry point in setup.py::
 
         [allura.theme]
         myprovider = foo.bar:MyThemeProvider
@@ -855,7 +855,7 @@ class UserPreferencesProvider(object):
     '''
     An interface for user preferences, like display_name and email_address
 
-    To use a new provider, expose an entry point in setup.py:
+    To use a new provider, expose an entry point in setup.py::
 
         [allura.user_prefs]
         myprefs = foo.bar:MyUserPrefProvider

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/df942cb2/Allura/docs/api/app.rst
----------------------------------------------------------------------
diff --git a/Allura/docs/api/app.rst b/Allura/docs/api/app.rst
index 491d0b4..f99a0c2 100644
--- a/Allura/docs/api/app.rst
+++ b/Allura/docs/api/app.rst
@@ -22,15 +22,4 @@
 
 .. automodule:: allura.app
 
-  .. autoclass:: Application
-      :members:
-
-  .. autoclass:: ConfigOption
-      :members:
-
-  .. autoclass:: DefaultAdminController
-      :members:
-
-  .. autoclass:: SitemapEntry
-      :members:
-
+    :members:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/df942cb2/Allura/docs/api/lib/plugin.rst
----------------------------------------------------------------------
diff --git a/Allura/docs/api/lib/plugin.rst b/Allura/docs/api/lib/plugin.rst
new file mode 100644
index 0000000..77e619b
--- /dev/null
+++ b/Allura/docs/api/lib/plugin.rst
@@ -0,0 +1,24 @@
+..     Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+
+.. _plugin_module:
+
+:mod:`allura.lib.plugin`
+-------------------------------------
+
+.. automodule:: allura.lib.plugin
+    :members:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/df942cb2/Allura/docs/api/lib/spam.rst
----------------------------------------------------------------------
diff --git a/Allura/docs/api/lib/spam.rst b/Allura/docs/api/lib/spam.rst
new file mode 100644
index 0000000..b9558a3
--- /dev/null
+++ b/Allura/docs/api/lib/spam.rst
@@ -0,0 +1,24 @@
+..     Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+
+.. _spam_module:
+
+:mod:`allura.lib.spam`
+-------------------------------------
+
+.. automodule:: allura.lib.spam
+    :members:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/df942cb2/Allura/docs/extending.rst
----------------------------------------------------------------------
diff --git a/Allura/docs/extending.rst b/Allura/docs/extending.rst
new file mode 100644
index 0000000..a430202
--- /dev/null
+++ b/Allura/docs/extending.rst
@@ -0,0 +1,55 @@
+..     Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+
+Extending Allura with Entry Points
+===================================
+
+There are many extension points to extending Allura.  They all make themselves
+known to Allura via python entry points defined in ``setup.py``.  Many are then
+available immediately.  Others, such as authentication providers or themes, need
+to be specified in your ``.ini`` file, since you may only have one enabled at a time.
+
+The available extension points for Allura are:
+
+* :class:`allura.app.Application` (aka tool) and :class:`allura.app.Artifact`
+* :class:`allura.lib.plugin.ThemeProvider`
+* :class:`allura.lib.plugin.ProjectRegistrationProvider`
+* :class:`allura.lib.plugin.AuthenticationProvider`
+* :class:`allura.lib.plugin.UserPreferencesProvider`
+* :class:`allura.lib.plugin.AdminExtension`
+* :class:`allura.lib.spam.SpamFilter`
+* ``site_stats`` in the root API data.  Docs in :class:`allura.controllers.rest.RestController`
+* :mod:`allura.lib.package_path_loader` (for overriding templates)
+
+A listing of available 3rd-party extensions is at https://sourceforge.net/p/allura/wiki/Extensions/
+
+Other entry points are used to provider ``paster`` commands and ``easy_widget`` configuration.
+
+
+Event Handlers
+==============
+
+Another way to extend Allura is set up event handlers to respond to Allura events.
+There is documentation and examples at :ref:`events`.
+
+The events that allura publishes are:
+
+* project_created
+* project_updated
+* repo_cloned
+* repo_refreshed
+* repo_clone_task_failed
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/df942cb2/Allura/docs/guides/message_bus.rst
----------------------------------------------------------------------
diff --git a/Allura/docs/guides/message_bus.rst b/Allura/docs/guides/message_bus.rst
index f8285e1..11067c9 100644
--- a/Allura/docs/guides/message_bus.rst
+++ b/Allura/docs/guides/message_bus.rst
@@ -61,6 +61,8 @@ well::
 
     commit()
 
+.. _events:
+
 Events
 -------------------
 
@@ -82,7 +84,7 @@ Under the covers, this is scheduling an `event` task that calls all the handlers
 for a particular named event.  Note that you can pass arguments (\*args, and
 \*\*kwargs) to event handlers just like you do to tasks, with the exception that
 the topic name (above, this would be 'project_updated') is always the first
-parameter passed to the event handler.  
+parameter passed to the event handler.
 
 Running the Task Daemon
 ----------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/df942cb2/Allura/docs/index.rst
----------------------------------------------------------------------
diff --git a/Allura/docs/index.rst b/Allura/docs/index.rst
index 56b1b52..877b98d 100644
--- a/Allura/docs/index.rst
+++ b/Allura/docs/index.rst
@@ -57,12 +57,13 @@ Inside the Platform Components
    guides/message_bus
    guides/email
    guides/permissions
+   extending
 
 API Documentation
 ==================
 
 .. toctree::
-   :maxdepth: 1
+   :maxdepth: 2
    :glob:
 
    api/*


[24/25] git commit: [#6464] Fixed issues from rebase

Posted by jo...@apache.org.
[#6464] Fixed issues from rebase

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/2844182c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/2844182c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/2844182c

Branch: refs/heads/cj/6464
Commit: 2844182ce3768736d53ad5649c7b595e6f6c5ab9
Parents: 980111a
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Wed Aug 14 16:26:53 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 14 16:26:53 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/__init__.py        |  7 ++-----
 ForgeImporters/forgeimporters/google/tests/test_code.py |  4 +++-
 .../forgeimporters/tests/google/test_extractor.py       | 12 +++++++++---
 .../forgeimporters/tests/google/test_tracker.py         |  4 +++-
 4 files changed, 17 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2844182c/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 67ef14d..1015905 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -79,7 +79,6 @@ class GoogleCodeProjectExtractor(object):
     DEFAULT_ICON = 'http://www.gstatic.com/codesite/ph/images/defaultlogo.png'
 
     def __init__(self, project_name, page_name=None, **kw):
-        self.url = self.PAGE_MAP[page_name].format(
         self.project_name = project_name
         self._page_cache = {}
         self.url = None
@@ -105,7 +104,7 @@ class GoogleCodeProjectExtractor(object):
         if self.url in self._page_cache:
             self.page = self._page_cache[self.url]
         else:
-            self.page = self._page_cache[page_name_or_url] = \
+            self.page = self._page_cache[self.url] = \
                     BeautifulSoup(urllib2.urlopen(self.url))
         return self.page
 
@@ -116,9 +115,7 @@ class GoogleCodeProjectExtractor(object):
 
         """
         return self.PAGE_MAP[page_name].format(
-            project_name = urllib.quote(self.project_name),
-            **kw,
-        )
+            project_name = urllib.quote(self.project_name), **kw)
 
     def get_short_description(self, project):
         page = self.get_page('project_info')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2844182c/ForgeImporters/forgeimporters/google/tests/test_code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tests/test_code.py b/ForgeImporters/forgeimporters/google/tests/test_code.py
index 13ec0c3..950246a 100644
--- a/ForgeImporters/forgeimporters/google/tests/test_code.py
+++ b/ForgeImporters/forgeimporters/google/tests/test_code.py
@@ -56,9 +56,10 @@ class TestGoogleRepoImporter(TestCase):
         project.get_tool_data.side_effect = lambda *args: gc_proj_name
         return project
 
+    @patch('forgeimporters.google.code.g')
     @patch('forgeimporters.google.code.GoogleCodeProjectExtractor')
     @patch('forgeimporters.google.code.get_repo_url')
-    def test_import_tool_happy_path(self, get_repo_url, gcpe):
+    def test_import_tool_happy_path(self, get_repo_url, gcpe, g):
         gcpe.return_value.get_repo_type.return_value = 'git'
         get_repo_url.return_value = 'http://remote/clone/url/'
         p = self._make_project(gc_proj_name='myproject')
@@ -70,6 +71,7 @@ class TestGoogleRepoImporter(TestCase):
                 mount_label='Code',
                 init_from_url='http://remote/clone/url/',
                 )
+        g.post_event.assert_called_once_with('project_updated')
 
 
 class TestGoogleRepoImportController(TestController, TestCase):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2844182c/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index 78176f8..77b1a34 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -44,14 +44,20 @@ class TestGoogleCodeProjectExtractor(TestCase):
         self.assertEqual(extractor.page, self.soup.return_value)
 
     def test_get_page(self):
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
+        self.assertEqual(1, self.urlopen.call_count)
+        page = extractor.get_page('project_info')
         self.assertEqual(1, self.urlopen.call_count)
+        self.assertEqual(page, extractor._page_cache['http://code.google.com/p/my-project/'])
         page = extractor.get_page('project_info')
         self.assertEqual(1, self.urlopen.call_count)
-        self.assertEqual(page, extractor._page_cache['project_info'])
+        self.assertEqual(page, extractor._page_cache['http://code.google.com/p/my-project/'])
+        page = extractor.get_page('source_browse')
+        self.assertEqual(2, self.urlopen.call_count)
+        self.assertEqual(page, extractor._page_cache['http://code.google.com/p/my-project/source/browse/'])
 
     def test_get_page_url(self):
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project')
+        extractor = google.GoogleCodeProjectExtractor('my-project')
         self.assertEqual(extractor.get_page_url('project_info'),
                 'http://code.google.com/p/my-project/')
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2844182c/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index 70ea3ad..15b8f33 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -24,13 +24,14 @@ from ...google import tracker
 
 
 class TestTrackerImporter(TestCase):
+    @mock.patch.object(tracker, 'g')
     @mock.patch.object(tracker, 'c')
     @mock.patch.object(tracker, 'ThreadLocalORMSession')
     @mock.patch.object(tracker, 'session')
     @mock.patch.object(tracker, 'M')
     @mock.patch.object(tracker, 'TM')
     @mock.patch.object(tracker, 'GoogleCodeProjectExtractor')
-    def test_import_tool(self, gpe, TM, M, session, tlos, c):
+    def test_import_tool(self, gpe, TM, M, session, tlos, c, g):
         importer = tracker.GoogleCodeTrackerImporter()
         importer.process_fields = mock.Mock()
         importer.process_labels = mock.Mock()
@@ -70,6 +71,7 @@ class TestTrackerImporter(TestCase):
                 mock.call(tickets[0]),
                 mock.call(tickets[1]),
             ])
+        g.post_event.assert_called_once_with('project_updated')
 
     def test_custom_fields(self):
         importer = tracker.GoogleCodeTrackerImporter()


[07/25] git commit: [#6553] fix taskd error logging (again)

Posted by jo...@apache.org.
[#6553] fix taskd error logging (again)

* don't re-raise an error after we've logged it already
* commit [2b7892] was incorrect in saying that errormiddleware wasn't used for taskd,
only StatusCodeRedirect was bypassed.  Now we *do* bypass error middleware also


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/9a13632b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/9a13632b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/9a13632b

Branch: refs/heads/cj/6464
Commit: 9a13632b5524702b4131fa33719c015cb4f2c58f
Parents: fea4b5f
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Mon Aug 12 19:33:17 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Tue Aug 13 21:15:25 2013 +0000

----------------------------------------------------------------------
 Allura/allura/config/middleware.py | 12 ++++++------
 Allura/allura/model/monq_model.py  |  1 -
 2 files changed, 6 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9a13632b/Allura/allura/config/middleware.py
----------------------------------------------------------------------
diff --git a/Allura/allura/config/middleware.py b/Allura/allura/config/middleware.py
index bd73dce..1e9b0c7 100644
--- a/Allura/allura/config/middleware.py
+++ b/Allura/allura/config/middleware.py
@@ -100,7 +100,7 @@ def _make_core_app(root, global_conf, full_stack=True, **app_conf):
 
     # Configure EW variable provider
     ew.render.TemplateEngine.register_variable_provider(get_tg_vars)
-    
+
     # Set FormEncode language to english, as we don't support any other locales
     formencode.api.set_stdtranslation(domain='FormEncode', languages=['en'])
 
@@ -161,16 +161,16 @@ def _make_core_app(root, global_conf, full_stack=True, **app_conf):
     #    streaming=true ensures they won't be cleaned up till
     #    the WSGI application's iterator is exhausted
     app = RegistryManager(app, streaming=True)
-    # Converts exceptions to HTTP errors, shows traceback in debug mode
-    tg.error.footer_html = '<!-- %s %s -->'  # don't use TG footer with extra CSS & images that take time to load
-    app = tg.error.ErrorHandler(app, global_conf, **config['pylons.errorware'])
     # Make sure that the wsgi.scheme is set appropriately when we
     # have the funky HTTP_X_SFINC_SSL  environ var
     if asbool(app_conf.get('auth.method', 'local')=='sfx'):
         app = set_scheme_middleware(app)
-    # Redirect some status codes to /error/document
+    # "task" wsgi would get a 2nd request to /error/document if we used this middleware
     if config.get('override_root') != 'task':
-        # "task" wsgi would get a 2nd request to /error/document if we used this middleware
+        # Converts exceptions to HTTP errors, shows traceback in debug mode
+        tg.error.footer_html = '<!-- %s %s -->'  # don't use TG footer with extra CSS & images that take time to load
+        app = tg.error.ErrorHandler(app, global_conf, **config['pylons.errorware'])
+        # Redirect some status codes to /error/document
         if asbool(config['debug']):
             app = StatusCodeRedirect(app, base_config.handle_status_codes)
         else:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9a13632b/Allura/allura/model/monq_model.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/monq_model.py b/Allura/allura/model/monq_model.py
index 9fa0064..3163ea5 100644
--- a/Allura/allura/model/monq_model.py
+++ b/Allura/allura/model/monq_model.py
@@ -264,7 +264,6 @@ class MonQTask(MappedClass):
                 log.error(self.result)
             else:
                 self.result = traceback.format_exc()
-            raise
         finally:
             self.time_stop = datetime.utcnow()
             session(self).flush(self)


[17/25] git commit: [#6464] Refactored plain2markdown to Allura helpers

Posted by jo...@apache.org.
[#6464] Refactored plain2markdown to Allura helpers

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/b94e8444
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/b94e8444
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/b94e8444

Branch: refs/heads/cj/6464
Commit: b94e8444761abc835963e6135adb28b9494f5fd1
Parents: f7da58e
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Thu Aug 8 18:49:51 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 14 15:41:12 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/helpers.py            | 44 ++++++++++++++++++++++++++++
 ForgeBlog/forgeblog/command/rssfeeds.py | 36 +----------------------
 2 files changed, 45 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/b94e8444/Allura/allura/lib/helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 1604aa9..9f6fbfc 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -78,6 +78,26 @@ re_clean_vardec_key = re.compile(r'''\A
 )+
 \Z''', re.VERBOSE)
 
+# markdown escaping regexps
+re_amp = re.compile(r'''
+    [&]          # amp
+    (?=          # look ahead for:
+      ([a-zA-Z0-9]+;)  # named HTML entity
+      |
+      (\#[0-9]+;)      # decimal entity
+      |
+      (\#x[0-9A-F]+;)  # hex entity
+    )
+    ''', re.VERBOSE)
+re_leading_spaces = re.compile(r'^[\t ]+', re.MULTILINE)
+re_preserve_spaces = re.compile(r'''
+    [ ]           # space
+    (?=[ ])       # lookahead for a space
+    ''', re.VERBOSE)
+re_angle_bracket_open = re.compile('<')
+re_angle_bracket_close = re.compile('>')
+md_chars_matcher_all = re.compile(r"([`\*_{}\[\]\(\)#!\\.+-])")
+
 def make_safe_path_portion(ustr, relaxed=True):
     """Return an ascii representation of ``ustr`` that conforms to mount point
     naming :attr:`rules <re_tool_mount_point_fragment>`.
@@ -857,3 +877,27 @@ def split_select_field_options(field_options):
         # so we're getting rid of those.
         field_options = [o.replace('"', '') for o in field_options]
     return field_options
+
+
+def plain2markdown(text, preserve_multiple_spaces=False, has_html_entities=False):
+    if not has_html_entities:
+        # prevent &foo; and &#123; from becoming HTML entities
+        text = re_amp.sub('&amp;', text)
+    # avoid accidental 4-space indentations creating code blocks
+    if preserve_multiple_spaces:
+        text = text.replace('\t', ' ' * 4)
+        text = re_preserve_spaces.sub('&nbsp;', text)
+    else:
+        text = re_leading_spaces.sub('', text)
+    try:
+        # try to use html2text for most of the escaping
+        import html2text
+        html2text.BODY_WIDTH = 0
+        text = html2text.escape_md_section(text, snob=True)
+    except ImportError:
+        # fall back to just escaping any MD-special chars
+        text = md_chars_matcher.sub(r"\\\\1", text)
+    # prevent < and > from becoming tags
+    text = re_angle_bracket_open.sub('&lt;', text)
+    text = re_angle_bracket_close.sub('&gt;', text)
+    return text

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/b94e8444/ForgeBlog/forgeblog/command/rssfeeds.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/command/rssfeeds.py b/ForgeBlog/forgeblog/command/rssfeeds.py
index 9f44fd4..8297c79 100644
--- a/ForgeBlog/forgeblog/command/rssfeeds.py
+++ b/ForgeBlog/forgeblog/command/rssfeeds.py
@@ -34,6 +34,7 @@ from forgeblog import version
 from forgeblog.main import ForgeBlogApp
 from allura.lib import exceptions
 from allura.lib.decorators import exceptionless
+from allura.lib.helpers import plain2markdown
 
 ## Everything in this file depends on html2text,
 ## so import attempt is placed in global scope.
@@ -45,41 +46,6 @@ except ImportError:
 
 html2text.BODY_WIDTH = 0
 
-re_amp = re.compile(r'''
-    [&]          # amp
-    (?=          # look ahead for:
-      ([a-zA-Z0-9]+;)  # named HTML entity
-      |
-      (\#[0-9]+;)      # decimal entity
-      |
-      (\#x[0-9A-F]+;)  # hex entity
-    )
-    ''', re.VERBOSE)
-re_leading_spaces = re.compile(r'^[\t ]+', re.MULTILINE)
-re_preserve_spaces = re.compile(r'''
-    [ ]           # space
-    (?=[ ])       # lookahead for a space
-    ''', re.VERBOSE)
-re_angle_bracket_open = re.compile('<')
-re_angle_bracket_close = re.compile('>')
-def plain2markdown(text, preserve_multiple_spaces=False, has_html_entities=False):
-    if not has_html_entities:
-        # prevent &foo; and &#123; from becoming HTML entities
-        text = re_amp.sub('&amp;', text)
-    # avoid accidental 4-space indentations creating code blocks
-    if preserve_multiple_spaces:
-        text = text.replace('\t', ' ' * 4)
-        text = re_preserve_spaces.sub('&nbsp;', text)
-    else:
-        text = re_leading_spaces.sub('', text)
-    # use html2text for most of the escaping
-    text = html2text.escape_md_section(text, snob=True)
-    # prevent < and > from becoming tags
-    text = re_angle_bracket_open.sub('&lt;', text)
-    text = re_angle_bracket_close.sub('&gt;', text)
-    return text
-
-
 class RssFeedsCommand(base.BlogCommand):
     summary = 'Rss feed client'
     parser = base.BlogCommand.standard_parser(verbose=True)


[06/25] git commit: [#6458] add GoogleCodeWikiImporter to requirements-sf.txt

Posted by jo...@apache.org.
[#6458] add GoogleCodeWikiImporter to requirements-sf.txt


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/fea4b5f7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/fea4b5f7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/fea4b5f7

Branch: refs/heads/cj/6464
Commit: fea4b5f78b8ce9101ac88b0dc8473e02adbdde14
Parents: d43c254
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Tue Aug 13 19:40:08 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Tue Aug 13 19:40:08 2013 +0000

----------------------------------------------------------------------
 requirements-sf.txt | 1 +
 1 file changed, 1 insertion(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/fea4b5f7/requirements-sf.txt
----------------------------------------------------------------------
diff --git a/requirements-sf.txt b/requirements-sf.txt
index 7ce28ae..b774faf 100644
--- a/requirements-sf.txt
+++ b/requirements-sf.txt
@@ -6,6 +6,7 @@ kombu==1.0.4
 coverage==3.5a1-20110413
 ForgeHg==0.1.15
 ForgePastebin==0.2.7
+GoogleCodeWikiImporter==0.1.0
 mechanize==0.2.4
 mercurial==1.4.3
 MySQL-python==1.2.3c1


[13/25] git commit: [#4818] add admin extensions

Posted by jo...@apache.org.
[#4818] add admin extensions


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/5f836baa
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/5f836baa
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/5f836baa

Branch: refs/heads/cj/6464
Commit: 5f836baa5b741bb24175233b90276df90a7fa11b
Parents: dfafc62
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Fri Aug 9 19:41:07 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Aug 14 13:12:13 2013 +0000

----------------------------------------------------------------------
 Allura/allura/ext/admin/admin_main.py        | 17 ++++++++++
 Allura/allura/lib/app_globals.py             |  1 +
 Allura/allura/lib/plugin.py                  | 32 +++++++++++++++++++
 Allura/allura/tests/functional/test_admin.py | 39 +++++++++++++++++++++++
 4 files changed, 89 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/5f836baa/Allura/allura/ext/admin/admin_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index f8c8983..92efacb 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -139,6 +139,10 @@ class AdminApp(Application):
             links.append(SitemapEntry('Statistics', nbhd_admin_url+ 'stats/'))
             links.append(None)
             links.append(SitemapEntry('Help', nbhd_admin_url+ 'help/'))
+
+        for name, admin_extension in g.entry_points['admin'].iteritems():
+            admin_extension().update_project_sidebar_menu(links)
+
         return links
 
     def admin_menu(self):
@@ -147,6 +151,18 @@ class AdminApp(Application):
     def install(self, project):
         pass
 
+
+class AdminExtensionLookup(object):
+    
+    @expose()
+    def _lookup(self, name, *remainder):
+        for ext_name, admin_extension in g.entry_points['admin'].iteritems():
+            controller = admin_extension().project_admin_controllers.get(name)
+            if controller:
+                return controller(), remainder
+        raise exc.HTTPNotFound, name
+
+
 class ProjectAdminController(BaseController):
 
     def _check_security(self):
@@ -156,6 +172,7 @@ class ProjectAdminController(BaseController):
         self.permissions = PermissionsController()
         self.groups = GroupsController()
         self.audit = AuditController()
+        self.ext = AdminExtensionLookup()
 
     @with_trailing_slash
     @expose('jinja:allura.ext.admin:templates/project_admin.html')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/5f836baa/Allura/allura/lib/app_globals.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index bdcacf7..7f45f69 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -197,6 +197,7 @@ class Globals(object):
             spam=_cache_eps('allura.spam'),
             stats=_cache_eps('allura.stats'),
             site_stats=_cache_eps('allura.site_stats'),
+            admin=_cache_eps('allura.admin'),
             )
 
         # Zarkov logger

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/5f836baa/Allura/allura/lib/plugin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index 4afd9fc..f972c26 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -915,3 +915,35 @@ class LocalUserPreferencesProvider(UserPreferencesProvider):
         users = M.User.query.find(dict(
                 display_name=name_regex)).sort('username').all()
         return users
+
+
+class AdminExtension(object):
+    """
+    A base class for extending the admin areas in Allura.
+
+    After extending this, expose the app by adding an entry point in your
+    setup.py::
+
+        [allura.admin]
+        myadmin = foo.bar.baz:MyCustomAdmin
+
+    :ivar dict project_admin_controllers: Mapping of str (url component) to
+        Controllers.  Can be implemented as a ``@property`` function.  The str
+        url components will be mounted at /p/someproject/admin/ext/STR/ and will
+        invoke the Controller.
+    """
+
+    project_admin_controllers = {}
+
+    def update_project_sidebar_menu(self, sidebar_links):
+        """
+        Implement this function to modify the project sidebar.
+        Check `c.project` if you want to limit when this displays
+        (e.g. nbhd project, subproject, etc)
+
+        :param sidebar_links: project admin side bar links
+        :type sidebar_links: list of :class:`allura.app.SitemapEntry`
+
+        :rtype: ``None``
+        """
+        pass

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/5f836baa/Allura/allura/tests/functional/test_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 7d630c9..adeb71b 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -24,6 +24,10 @@ from contextlib import contextmanager
 import PIL
 from nose.tools import assert_equals
 from ming.orm.ormsession import ThreadLocalORMSession
+from tg import expose
+from pylons import tmpl_context as c, app_globals as g
+import mock
+from BeautifulSoup import BeautifulSoup
 
 try:
     import sfx
@@ -33,6 +37,8 @@ except ImportError:
 from allura.tests import TestController
 from allura.tests import decorators as td
 from allura import model as M
+from allura.app import SitemapEntry
+from allura.lib.plugin import AdminExtension
 
 @contextmanager
 def audits(*messages):
@@ -726,3 +732,36 @@ class TestProjectAdmin(TestController):
         assert {u'text': u'Does not have permission create', u'has': u'no', u'name': u'create'} in r.json[admin_id]
         assert {u'text': u'Does not have permission create', u'has': u'no', u'name': u'create'} in r.json[mem_id]
         assert {u'text': u'Does not have permission create', u'has': u'no', u'name': u'create'} in r.json[anon_id]
+
+
+    def test_admin_extension_sidebar(self):
+
+        class FooSettingsController(object):
+            @expose()
+            def index(self, *a, **kw):
+                return 'here the foo settings go'
+
+
+        class FooSettingsExtension(AdminExtension):
+            def update_project_sidebar_menu(self, sidebar_links):
+                base_url = c.project.url()+'admin/ext/'
+                sidebar_links.append(SitemapEntry('Foo Settings', base_url+'foo'))
+
+            @property
+            def project_admin_controllers(self):
+                return {
+                    'foo': FooSettingsController,
+                }
+
+        eps = {
+            'admin': {
+                'foo-settings': FooSettingsExtension,
+            }
+        }
+
+        with mock.patch.dict(g.entry_points, eps):
+            main_page = self.app.get('/admin/')
+            foo_page = main_page.click(description='Foo Settings')
+            url = foo_page.environ['PATH_INFO']
+            assert url.endswith('/admin/ext/foo'), url
+            assert_equals('here the foo settings go', foo_page.body)


[02/25] git commit: [#6458] Refactored Google Code project extractor

Posted by jo...@apache.org.
[#6458] Refactored Google Code project extractor

- Introduced a caching get_page() method so that page fetching
  is no longer tightly coupled to extractor instantiation. In
  other words, you can fetch any page you want, regardless of
  the page that you instantiated the extractor with.

- Also removed the wiki-specific stuff and moved that to a
  subclass in the googlecodewikiimporter pkg.

Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/ee1c5914
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/ee1c5914
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/ee1c5914

Branch: refs/heads/cj/6464
Commit: ee1c5914a92961fb46326c59c456dbea860b10f1
Parents: 3600da2
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Thu Aug 8 13:33:42 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Aug 8 21:25:45 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/google/__init__.py           | 57 ++++++++++++++------
 .../tests/google/test_extractor.py              | 26 +++++----
 ForgeWiki/forgewiki/wiki_main.py                |  2 +-
 3 files changed, 57 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ee1c5914/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 8c91fd3..a307bcd 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -40,7 +40,6 @@ class GoogleCodeProjectExtractor(object):
     PAGE_MAP = {
             'project_info': BASE_URL + '/p/%s/',
             'source_browse': BASE_URL + '/p/%s/source/browse/',
-            'wiki_index': BASE_URL + '/p/%s/w/list',
         }
 
     LICENSE_MAP = defaultdict(lambda:'Other/Proprietary License', {
@@ -58,17 +57,49 @@ class GoogleCodeProjectExtractor(object):
 
     DEFAULT_ICON = 'http://www.gstatic.com/codesite/ph/images/defaultlogo.png'
 
-    def __init__(self, allura_project, gc_project_name, page):
+    def __init__(self, allura_project, gc_project_name, page=None):
         self.project = allura_project
         self.gc_project_name = gc_project_name
-        self.url = self.PAGE_MAP[page] % urllib.quote(self.gc_project_name)
-        self.page = BeautifulSoup(urllib2.urlopen(self.url))
+        self._page_cache = {}
+        self.url = None
+        self.page = None
+        if page:
+            self.get_page(page)
+
+    def get_page(self, page_name_or_url):
+        """Return a Beautiful soup object for the given page name or url.
+
+        If a page name is provided, the associated url is looked up in
+        :attr:`PAGE_MAP`.
+
+        Results are cached so that subsequent calls for the same page name or
+        url will return the cached result rather than making another HTTP
+        request.
+
+        """
+        if page_name_or_url in self._page_cache:
+            return self._page_cache[page_name_or_url]
+        self.url = (self.get_page_url(page_name_or_url) if page_name_or_url in
+                self.PAGE_MAP else page_name_or_url)
+        self.page = self._page_cache[page_name_or_url] = \
+                BeautifulSoup(urllib2.urlopen(self.url))
+        return self.page
+
+    def get_page_url(self, page_name):
+        """Return the url associated with ``page_name``.
+
+        Raises KeyError if ``page_name`` is not in :attr:`PAGE_MAP`.
+
+        """
+        return self.PAGE_MAP[page_name] % urllib.quote(self.gc_project_name)
 
     def get_short_description(self):
-        self.project.short_description = self.page.find(itemprop='description').string.strip()
+        page = self.get_page('project_info')
+        self.project.short_description = page.find(itemprop='description').string.strip()
 
     def get_icon(self):
-        icon_url = urljoin(self.url, self.page.find(itemprop='image').attrMap['src'])
+        page = self.get_page('project_info')
+        icon_url = urljoin(self.url, page.find(itemprop='image').attrMap['src'])
         if icon_url == self.DEFAULT_ICON:
             return
         icon_name = urllib.unquote(urlparse(icon_url).path).split('/')[-1]
@@ -81,12 +112,14 @@ class GoogleCodeProjectExtractor(object):
             thumbnail_meta={'project_id': self.project._id, 'category': 'icon'})
 
     def get_license(self):
-        license = self.page.find(text='Code license').findNext().find('a').string.strip()
+        page = self.get_page('project_info')
+        license = page.find(text='Code license').findNext().find('a').string.strip()
         trove = M.TroveCategory.query.get(fullname=self.LICENSE_MAP[license])
         self.project.trove_license.append(trove._id)
 
     def get_repo_type(self):
-        repo_type = self.page.find(id="crumb_root")
+        page = self.get_page('source_browse')
+        repo_type = page.find(id="crumb_root")
         if not repo_type:
             raise Exception("Couldn't detect repo type: no #crumb_root in "
                     "{0}".format(self.url))
@@ -95,11 +128,3 @@ class GoogleCodeProjectExtractor(object):
             return re_match.group(0)
         else:
             raise Exception("Unknown repo type: {0}".format(repo_type.text))
-
-    def get_wiki_pages(self):
-        RE_WIKI_PAGE_URL = r'^/p/{0}/wiki/.*$'.format(self.gc_project_name)
-        seen = set()
-        for a in self.page.find(id="resultstable").findAll("a"):
-            if re.match(RE_WIKI_PAGE_URL, a['href']) and a['href'] not in seen:
-                yield (a.text, self.BASE_URL + a['href'])
-                seen.add(a['href'])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ee1c5914/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index 250759f..b4e64c0 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -43,6 +43,18 @@ class TestGoogleCodeProjectExtractor(TestCase):
         self.soup.assert_called_once_with(self.urlopen.return_value)
         self.assertEqual(extractor.page, self.soup.return_value)
 
+    def test_get_page(self):
+        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        self.assertEqual(1, self.urlopen.call_count)
+        page = extractor.get_page('project_info')
+        self.assertEqual(1, self.urlopen.call_count)
+        self.assertEqual(page, extractor._page_cache['project_info'])
+
+    def test_get_page_url(self):
+        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project')
+        self.assertEqual(extractor.get_page_url('project_info'),
+                'http://code.google.com/p/my-project/')
+
     def test_get_short_description(self):
         extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
         extractor.page.find.return_value.string = 'My Super Project'
@@ -93,9 +105,10 @@ class TestGoogleCodeProjectExtractor(TestCase):
 
     def _make_extractor(self, html):
         from BeautifulSoup import BeautifulSoup
-        with mock.patch.object(google, 'urllib2') as urllib2:
-            extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        with mock.patch.object(google, 'urllib2'):
+            extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project')
         extractor.page = BeautifulSoup(html)
+        extractor.get_page = lambda pagename: extractor.page
         extractor.url="http://test/source/browse"
         return extractor
 
@@ -118,12 +131,3 @@ class TestGoogleCodeProjectExtractor(TestCase):
         with self.assertRaises(Exception) as cm:
             extractor.get_repo_type()
         self.assertEqual(str(cm.exception), "Unknown repo type: cvs")
-
-    def test_get_wiki_pages(self):
-        extractor = self._make_extractor('''
-        <div id="resultstable">
-            <a href="#">Link that's not a wiki page</a>
-            <a href="/p/my-project/wiki/PageOne">PageOne</a>
-        </div>''')
-        self.assertEqual(list(extractor.get_wiki_pages()), [
-            ('PageOne', 'http://code.google.com/p/my-project/wiki/PageOne')])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ee1c5914/ForgeWiki/forgewiki/wiki_main.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/wiki_main.py b/ForgeWiki/forgewiki/wiki_main.py
index 064a9cd..816db78 100644
--- a/ForgeWiki/forgewiki/wiki_main.py
+++ b/ForgeWiki/forgewiki/wiki_main.py
@@ -139,7 +139,7 @@ class ForgeWikiApp(Application):
             elif new_root_page_name != self.default_root_page_name:
                 globals = WM.Globals(app_config_id=self.config._id, root=new_root_page_name)
             if globals is not None:
-                session(globals).flush()
+                session(globals).flush(globals)
 
     @Property
     def show_discussion():


[09/25] git commit: [#5177] Fixed default values for source and target_branch when creating a merge request

Posted by jo...@apache.org.
[#5177] Fixed default values for source and target_branch when creating a merge request

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/45732c0c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/45732c0c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/45732c0c

Branch: refs/heads/cj/6464
Commit: 45732c0cd2952b6e2d156f4f2ec1a21f20f9b317
Parents: 9a13632
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Thu Aug 8 14:03:29 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Tue Aug 13 21:28:33 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/repository.py         | 14 +++++++++++---
 Allura/allura/lib/repository.py                 |  5 ++++-
 Allura/allura/templates/repo/request_merge.html |  2 +-
 3 files changed, 16 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/45732c0c/Allura/allura/controllers/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/repository.py b/Allura/allura/controllers/repository.py
index aaf9f6a..36b4bdc 100644
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.py
@@ -162,9 +162,16 @@ class RepoRootController(BaseController, FeedController):
     def request_merge(self, branch=None, **kw):
         security.require(security.has_access(c.app.repo, 'admin'))
         c.form = self.mr_widget
-        if branch is None:
-            source_branch=c.app.default_branch_name
-        return dict(source_branch=source_branch)
+        if branch in c.form.source_branches:
+            source_branch = branch
+        else:
+            source_branch = c.app.default_branch_name
+        with c.app.repo.push_upstream_context():
+            target_branch = c.app.default_branch_name
+        return {
+                'source_branch': source_branch,
+                'target_branch': target_branch,
+            }
 
     @expose()
     @require_post()
@@ -417,6 +424,7 @@ class CommitBrowser(BaseController):
     def __init__(self, revision):
         self._revision = revision
         self._commit = c.app.repo.commit(revision)
+        c.revision = revision
         if self._commit is None:
             raise exc.HTTPNotFound
         self.tree = self.TreeBrowserClass(self._commit, tree=self._commit.tree)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/45732c0c/Allura/allura/lib/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/repository.py b/Allura/allura/lib/repository.py
index 83df6b2..65b68c8 100644
--- a/Allura/allura/lib/repository.py
+++ b/Allura/allura/lib/repository.py
@@ -127,7 +127,10 @@ class RepositoryApp(Application):
                     self.repo.upstream_repo.name)
                 ]
             if not c.app.repo.is_empty() and has_access(c.app.repo, 'admin'):
-                links.append(SitemapEntry('Request Merge', c.app.url + 'request_merge',
+                merge_url = c.app.url + 'request_merge'
+                if getattr(c, 'revision', None):
+                    merge_url = merge_url + '?branch=' + h.urlquote(c.revision)
+                links.append(SitemapEntry('Request Merge', merge_url,
                              ui_icon=g.icons['merge'],
                              ))
             pending_upstream_merges = self.repo.pending_upstream_merges()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/45732c0c/Allura/allura/templates/repo/request_merge.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/repo/request_merge.html b/Allura/allura/templates/repo/request_merge.html
index 02b9061..a55a67c 100644
--- a/Allura/allura/templates/repo/request_merge.html
+++ b/Allura/allura/templates/repo/request_merge.html
@@ -29,5 +29,5 @@
 {% block header %}Request merge of {{c.app.config.options.mount_label}} {% endblock %}
 
 {% block content %}
-  {{ c.form.display(action='do_request_merge', value=dict(source_branch=source_branch))}}
+  {{ c.form.display(action='do_request_merge', value=dict(source_branch=source_branch, target_branch=target_branch))}}
 {% endblock %}


[15/25] git commit: [#6464] Google Code Tracker Importer via web scraping

Posted by jo...@apache.org.
[#6464] Google Code Tracker Importer via web scraping

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/c3ab8456
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/c3ab8456
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/c3ab8456

Branch: refs/heads/cj/6464
Commit: c3ab8456b0f27a2ef5f19d17d6d0d6723731da73
Parents: 6657808
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue Aug 6 23:47:14 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 14 15:40:28 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/site_admin.py         |  15 +-
 .../allura/templates/site_admin_task_view.html  |   8 +
 .../forgeimporters/google/__init__.py           | 176 ++++++++++++++---
 ForgeImporters/forgeimporters/google/tasks.py   |   8 +-
 ForgeImporters/forgeimporters/google/tracker.py | 191 ++++---------------
 .../tests/google/test_extractor.py              |  19 +-
 .../forgeimporters/tests/google/test_tasks.py   |   8 +-
 .../forgeimporters/tests/google/test_tracker.py |  98 +++++-----
 8 files changed, 277 insertions(+), 246 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c3ab8456/Allura/allura/controllers/site_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/site_admin.py b/Allura/allura/controllers/site_admin.py
index 4f8f835..50b0e38 100644
--- a/Allura/allura/controllers/site_admin.py
+++ b/Allura/allura/controllers/site_admin.py
@@ -30,6 +30,7 @@ import tg
 from pylons import tmpl_context as c, app_globals as g
 from pylons import request
 from formencode import validators, Invalid
+from webob.exc import HTTPNotFound
 
 from allura.lib import helpers as h
 from allura.lib import validators as v
@@ -321,7 +322,19 @@ class TaskManagerController(object):
             config_dict['user'] = user
         with h.push_config(c, **config_dict):
             task = task.post(*args, **kw)
-        redirect('view/%s' % task._id)
+        redirect('../view/%s' % task._id)
+
+    @expose()
+    @require_post()
+    def resubmit(self, task_id):
+        try:
+            task = M.monq_model.MonQTask.query.get(_id=bson.ObjectId(task_id))
+        except bson.errors.InvalidId as e:
+            task = None
+        if task is None:
+            raise HTTPNotFound()
+        task.state = 'ready'
+        redirect('../view/%s' % task._id)
 
     @expose('json:')
     def task_doc(self, task_name):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c3ab8456/Allura/allura/templates/site_admin_task_view.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/site_admin_task_view.html b/Allura/allura/templates/site_admin_task_view.html
index c107382..e363b8d 100644
--- a/Allura/allura/templates/site_admin_task_view.html
+++ b/Allura/allura/templates/site_admin_task_view.html
@@ -66,6 +66,9 @@
     #task_details td.second-column {
         border: 0;
     }
+    #resubmit-task-form {
+        float: right;
+    }
 </style>
 {% endblock %}
 
@@ -73,6 +76,11 @@
 {% if not task %}
     Task not found
 {% else %}
+    {% if task.state in ['error', 'complete'] %}
+    <form id="resubmit-task-form" action="../resubmit/{{task._id}}" method="POST">
+        <input type="submit" value="Re-Submit Task" />
+    </form>
+    {% endif %}
     <h2>Task Details</h2>
     <table id="task_details">
         <tr>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c3ab8456/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index a307bcd..5f09cb9 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -20,6 +20,7 @@ import urllib
 import urllib2
 from urlparse import urlparse, urljoin
 from collections import defaultdict
+from contextlib import closing
 try:
     from cStringIO import StringIO
 except ImportError:
@@ -33,13 +34,32 @@ from allura import model as M
 
 log = logging.getLogger(__name__)
 
+def _as_text(node, chunks=None):
+    """
+    Similar to node.text, but preserves whitespace around tags,
+    and converts <br/>s to \n.
+    """
+    if chunks is None:
+        chunks = []
+    for n in node:
+        if isinstance(n, basestring):
+            chunks.append(n)
+        elif n.name == 'br':
+            chunks.append('\n')
+        else:
+            _as_text(n, chunks)
+    return ''.join(chunks)
+
+
 class GoogleCodeProjectExtractor(object):
     BASE_URL = 'http://code.google.com'
     RE_REPO_TYPE = re.compile(r'(svn|hg|git)')
 
     PAGE_MAP = {
-            'project_info': BASE_URL + '/p/%s/',
-            'source_browse': BASE_URL + '/p/%s/source/browse/',
+            'project_info': BASE_URL + '/p/{project_name}/',
+            'source_browse': BASE_URL + '/p/{project_name}/source/browse/',
+            'issues_csv': BASE_URL + '/p/{project_name}/issues/csv?can=1&colspec=ID&start={start}',
+            'issue': BASE_URL + '/p/{project_name}/issues/detail?id={issue_id}',
         }
 
     LICENSE_MAP = defaultdict(lambda:'Other/Proprietary License', {
@@ -57,16 +77,16 @@ class GoogleCodeProjectExtractor(object):
 
     DEFAULT_ICON = 'http://www.gstatic.com/codesite/ph/images/defaultlogo.png'
 
-    def __init__(self, allura_project, gc_project_name, page=None):
-        self.project = allura_project
-        self.gc_project_name = gc_project_name
+    def __init__(self, project_name, page_name=None, **kw):
+        self.url = self.PAGE_MAP[page_name].format(
+        self.project_name = project_name
         self._page_cache = {}
         self.url = None
         self.page = None
-        if page:
-            self.get_page(page)
+        if page_name:
+            self.get_page(page_name, **kw)
 
-    def get_page(self, page_name_or_url):
+    def get_page(self, page_name_or_url, **kw):
         """Return a Beautiful soup object for the given page name or url.
 
         If a page name is provided, the associated url is looked up in
@@ -77,27 +97,33 @@ class GoogleCodeProjectExtractor(object):
         request.
 
         """
-        if page_name_or_url in self._page_cache:
-            return self._page_cache[page_name_or_url]
-        self.url = (self.get_page_url(page_name_or_url) if page_name_or_url in
-                self.PAGE_MAP else page_name_or_url)
-        self.page = self._page_cache[page_name_or_url] = \
-                BeautifulSoup(urllib2.urlopen(self.url))
+        if page_name_or_url in self.PAGE_MAP:
+            self.url = self.get_page_url(page_name_or_url, **kw)
+        else:
+            self.url = page_name_or_url
+        if self.url in self._page_cache:
+            self.page = self._page_cache[self.url]
+        else:
+            self.page = self._page_cache[page_name_or_url] = \
+                    BeautifulSoup(urllib2.urlopen(self.url))
         return self.page
 
-    def get_page_url(self, page_name):
+    def get_page_url(self, page_name, **kw):
         """Return the url associated with ``page_name``.
 
         Raises KeyError if ``page_name`` is not in :attr:`PAGE_MAP`.
 
         """
-        return self.PAGE_MAP[page_name] % urllib.quote(self.gc_project_name)
+        return self.PAGE_MAP[page_name].format(
+            project_name = urllib.quote(self.project_name),
+            **kw,
+        )
 
-    def get_short_description(self):
+    def get_short_description(self, project):
         page = self.get_page('project_info')
-        self.project.short_description = page.find(itemprop='description').string.strip()
+        project.short_description = page.find(itemprop='description').string.strip()
 
-    def get_icon(self):
+    def get_icon(self, project):
         page = self.get_page('project_info')
         icon_url = urljoin(self.url, page.find(itemprop='image').attrMap['src'])
         if icon_url == self.DEFAULT_ICON:
@@ -109,13 +135,13 @@ class GoogleCodeProjectExtractor(object):
             icon_name, fp,
             fp_ish.info()['content-type'].split(';')[0],  # strip off charset=x extra param,
             square=True, thumbnail_size=(48,48),
-            thumbnail_meta={'project_id': self.project._id, 'category': 'icon'})
+            thumbnail_meta={'project_id': project._id, 'category': 'icon'})
 
-    def get_license(self):
+    def get_license(self, project):
         page = self.get_page('project_info')
         license = page.find(text='Code license').findNext().find('a').string.strip()
         trove = M.TroveCategory.query.get(fullname=self.LICENSE_MAP[license])
-        self.project.trove_license.append(trove._id)
+        project.trove_license.append(trove._id)
 
     def get_repo_type(self):
         page = self.get_page('source_browse')
@@ -128,3 +154,109 @@ class GoogleCodeProjectExtractor(object):
             return re_match.group(0)
         else:
             raise Exception("Unknown repo type: {0}".format(repo_type.text))
+
+    @classmethod
+    def _get_issue_ids_page(cls, project_name, start):
+        url = cls.PAGE_MAP['issues_csv'].format(project_name=project_name, start=start)
+        with closing(urllib2.urlopen(url)) as fp:
+            lines = fp.readlines()[1:]  # skip CSV header
+            if not lines[-1].startswith('"'):
+                lines.pop()  # skip "next page here" info footer
+        issue_ids = [line.strip('",\n') for line in lines]
+        return issue_ids
+
+    @classmethod
+    def iter_issues(cls, project_name):
+        """
+        Iterate over all issues for a project,
+        using paging to keep the responses reasonable.
+        """
+        start = 0
+        limit = 100
+
+        while True:
+            issue_ids = cls._get_issue_ids_page(project_name, start)
+            if len(issue_ids) <= 0:
+                return
+            for issue_id in issue_ids:
+                yield cls(project_name, 'issue', issue_id=issue_id)
+            start += limit
+
+    def get_issue_summary(self):
+        return self.page.find(id='issueheader').findAll('td', limit=2)[1].span.string.strip()
+
+    def get_issue_description(self):
+        return _as_text(self.page.find(id='hc0').pre)
+
+    def get_issue_created_date(self):
+        return self.page.find(id='hc0').find('span', 'date').get('title')
+
+    def get_issue_mod_date(self):
+        last_update = Comment(self.page.findAll('div', 'issuecomment')[-1])
+        return last_update.created_date
+
+    def get_issue_creator(self):
+        a = self.page.find(id='hc0').find('a', 'userlink')
+        return UserLink(a)
+
+    def get_issue_status(self):
+        return self.page.find(id='issuemeta').find('th', text=re.compile('Status:')).findNext().span.string.strip()
+
+    def get_issue_owner(self):
+        return UserLink(self.page.find(id='issuemeta').find('th', text=re.compile('Owner:')).findNext().a)
+
+    def get_issue_labels(self):
+        label_nodes = self.page.find(id='issuemeta').findAll('a', 'label')
+        return [_as_text(l) for l in label_nodes]
+
+    def get_issue_attachments(self):
+        attachments = self.page.find(id='hc0').find('div', 'attachments')
+        if attachments:
+            return map(Attachment, attachments.findAll('tr'))
+        else:
+            return []
+
+    def iter_comments(self):
+        for comment in self.page.findAll('div', 'issuecomment'):
+            yield Comment(comment)
+
+class UserLink(object):
+    def __init__(self, tag):
+        self.name = tag.string.strip()
+        self.link = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.get('href'))
+
+class Comment(object):
+    def __init__(self, tag):
+        self.author = UserLink(tag.find('span', 'author').find('a', 'userlink'))
+        self.created_date = tag.find('span', 'date').get('title')
+        self.body = _as_text(tag.find('pre'))
+        self._get_updates(tag)
+        self._get_attachments(tag)
+
+    def _get_updates(self, tag):
+        _updates = tag.find('div', 'updates')
+        if _updates:
+            _strings = _updates.findAll(text=True)
+            updates = (s.strip() for s in _strings if s.strip())
+            self.updates = {field: updates.next() for field in updates}
+        else:
+            self.updates = {}
+
+    def _get_attachments(self, tag):
+        attachments = tag.find('div', 'attachments')
+        if attachments:
+            self.attachments = map(Attachment, attachments.findAll('tr'))
+        else:
+            self.attachments = []
+
+class Attachment(object):
+    def __init__(self, tag):
+        self.filename = _as_text(tag).strip().split()[0]
+        self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.a.get('href'))
+        self.type = None
+
+    @property
+    def file(self):
+        fp_ish = urllib2.urlopen(self.url)
+        fp = StringIO(fp_ish.read())
+        return fp

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c3ab8456/ForgeImporters/forgeimporters/google/tasks.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tasks.py b/ForgeImporters/forgeimporters/google/tasks.py
index 968d9a9..69e7556 100644
--- a/ForgeImporters/forgeimporters/google/tasks.py
+++ b/ForgeImporters/forgeimporters/google/tasks.py
@@ -27,9 +27,9 @@ from . import GoogleCodeProjectExtractor
 
 @task
 def import_project_info(project_name):
-    extractor = GoogleCodeProjectExtractor(c.project, project_name, 'project_info')
-    extractor.get_short_description()
-    extractor.get_icon()
-    extractor.get_license()
+    extractor = GoogleCodeProjectExtractor(project_name, 'project_info')
+    extractor.get_short_description(c.project)
+    extractor.get_icon(c.project)
+    extractor.get_license(c.project)
     ThreadLocalORMSession.flush_all()
     g.post_event('project_updated')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c3ab8456/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 95f53e4..297f65a 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -19,15 +19,14 @@ from collections import defaultdict
 from datetime import datetime
 
 from pylons import tmpl_context as c
-#import gdata
-gdata = None
-from ming.orm import session
+from ming.orm import session, ThreadLocalORMSession
 
 from allura.lib import helpers as h
 
 from forgetracker.tracker_main import ForgeTrackerApp
 from forgetracker import model as TM
 from ..base import ToolImporter
+from . import GoogleCodeProjectExtractor
 
 
 class GoogleCodeTrackerImporter(ToolImporter):
@@ -42,23 +41,22 @@ class GoogleCodeTrackerImporter(ToolImporter):
             type='select',
         )
 
-    def import_tool(self, project, user, project_name=None, mount_point=None,
+    def import_tool(self, project, user, project_name, mount_point=None,
             mount_label=None, **kw):
-        c.app = project.install_app('tracker', mount_point, mount_label)
-        c.app.globals.open_status_names = ['New', 'Accepted', 'Started']
-        c.app.globals.closed_status_names = ['Fixed', 'Verified', 'Invalid', 'Duplicate', 'WontFix', 'Done']
+        c.app = project.install_app('tickets', mount_point, mount_label)
+        ThreadLocalORMSession.flush_all()
+        c.app.globals.open_status_names = 'New Accepted Started'
+        c.app.globals.closed_status_names = 'Fixed Verified Invalid Duplicate WontFix Done'
         self.custom_fields = {}
-        extractor = GDataAPIExtractor(project_name)
-        for issue in extractor.iter_issues():
+        for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
             ticket = TM.Ticket.new()
             self.process_fields(ticket, issue)
             self.process_labels(ticket, issue)
-            self.process_comments(ticket, extractor.iter_comments(issue))
+            self.process_comments(ticket, issue)
             session(ticket).flush(ticket)
             session(ticket).expunge(ticket)
         self.postprocess_custom_fields()
-        session(c.app).flush(c.app)
-        session(c.app.globals).flush(c.app.globals)
+        ThreadLocalORMSession.flush_all()
 
     def custom_field(self, name):
         if name not in self.custom_fields:
@@ -71,16 +69,25 @@ class GoogleCodeTrackerImporter(ToolImporter):
         return self.custom_fields[name]
 
     def process_fields(self, ticket, issue):
-        ticket.summary = issue.summary
-        ticket.description = issue.description
-        ticket.status = issue.status
-        ticket.created_date = datetime.strptime(issue.created_date, '')
-        ticket.mod_date = datetime.strptime(issue.mod_date, '')
+        ticket.summary = issue.get_issue_summary()
+        ticket.status = issue.get_issue_status()
+        ticket.created_date = datetime.strptime(issue.get_issue_created_date(), '%c')
+        ticket.mod_date = datetime.strptime(issue.get_issue_mod_date(), '%c')
+        ticket.description = (
+                u'*Originally created by:* [{creator.name}]({creator.link})\n'
+                '*Originally owned by:* [{owner.name}]({owner.link})\n'
+                '\n'
+                '{body}').format(
+                    creator=issue.get_issue_creator(),
+                    owner=issue.get_issue_owner(),
+                    body=issue.get_issue_description(),
+                )
+        ticket.add_multiple_attachments(issue.get_issue_attachments())
 
     def process_labels(self, ticket, issue):
         labels = set()
         custom_fields = defaultdict(set)
-        for label in issue.labels:
+        for label in issue.get_issue_labels():
             if u'-' in label:
                 name, value = label.split(u'-', 1)
                 cf = self.custom_field(name)
@@ -91,23 +98,24 @@ class GoogleCodeTrackerImporter(ToolImporter):
         ticket.labels = list(labels)
         ticket.custom_fields = {n: u', '.join(sorted(v)) for n,v in custom_fields.iteritems()}
 
-    def process_comments(self, ticket, comments):
-        for comment in comments:
-            p = ticket.thread.add_post(
+    def process_comments(self, ticket, issue):
+        for comment in issue.iter_comments():
+            p = ticket.discussion_thread.add_post(
                     text = (
-                        u'Originally posted by: [{author.name}]({author.link})\n'
+                        u'*Originally posted by:* [{author.name}]({author.link})\n'
                         '\n'
                         '{body}\n'
                         '\n'
                         '{updates}').format(
                             author=comment.author,
-                            body=comment.text,
+                            body=comment.body,
                             updates='\n'.join(
-                                '*%s*: %s' % (k,v)
+                                '**%s** %s' % (k,v)
                                 for k,v in comment.updates.items()
                             ),
                     )
                 )
+            p.created_date = p.timestamp = datetime.strptime(comment.created_date, '%c')
             p.add_multiple_attachments(comment.attachments)
 
     def postprocess_custom_fields(self):
@@ -125,138 +133,3 @@ class GoogleCodeTrackerImporter(ToolImporter):
             else:
                 field['options'] = ''
             c.app.globals.custom_fields.append(field)
-
-
-class GDataAPIExtractor(object):
-    def __init__(self, project_name):
-        self.project_name = project_name
-
-    def iter_issues(self, limit=50):
-        """
-        Iterate over all issues for a project,
-        using paging to keep the responses reasonable.
-        """
-        start = 1
-
-        client = gdata.projecthosting.client.ProjectHostingClient()
-        while True:
-            query = gdata.projecthosting.client.Query(start_index=start, max_results=limit)
-            issues = client.get_issues(self.project_name, query=query).entry
-            if len(issues) <= 0:
-                return
-            for issue in issues:
-                yield GDataAPIIssue(issue)
-            start += limit
-
-    def iter_comments(self, issue, limit=50):
-        """
-        Iterate over all comments for a given issue,
-        using paging to keep the responses reasonable.
-        """
-        start = 1
-
-        client = gdata.projecthosting.client.ProjectHostingClient()
-        while True:
-            query = gdata.projecthosting.client.Query(start_index=start, max_results=limit)
-            comments = client.get_comments(self.project_name, query=query).entry
-            if len(comments) <= 0:
-                return
-            for comment in comments:
-                yield GDataAPIComment(comment)
-            start += limit
-
-
-class GDataAPIUser(object):
-    def __init__(self, user):
-        self.user = user
-
-    @property
-    def name(self):
-        return h.really_unicode(self.user.name.text)
-
-    @property
-    def link(self):
-        return u'http://code.google.com/u/%s' % self.name
-
-
-class GDataAPIIssue(object):
-    def __init__(self, issue):
-        self.issue = issue
-
-    @property
-    def summary(self):
-        return h.really_unicode(self.issue.title.text)
-
-    @property
-    def description(self):
-        return h.really_unicode(self.issue.content.text)
-
-    @property
-    def created_date(self):
-        return self.to_date(self.issue.published.text)
-
-    @property
-    def mod_date(self):
-        return self.to_date(self.issue.updated.text)
-
-    @property
-    def creator(self):
-        return h.really_unicode(self.issue.author[0].name.text)
-
-    @property
-    def status(self):
-        if getattr(self.issue, 'status', None) is not None:
-            return h.really_unicode(self.issue.status.text)
-        return u''
-
-    @property
-    def owner(self):
-        if getattr(self.issue, 'owner', None) is not None:
-            return h.really_unicode(self.issue.owner.username.text)
-        return u''
-
-    @property
-    def labels(self):
-        return [h.really_unicode(l.text) for l in self.issue.labels]
-
-
-class GDataAPIComment(object):
-    def __init__(self, comment):
-        self.comment = comment
-
-    @property
-    def author(self):
-        return GDataAPIUser(self.comment.author[0])
-
-    @property
-    def created_date(self):
-        return h.really_unicode(self.comment.published.text)
-
-    @property
-    def body(self):
-        return h.really_unicode(self.comment.content.text)
-
-    @property
-    def updates(self):
-        return {}
-
-    @property
-    def attachments(self):
-        return []
-
-
-class GDataAPIAttachment(object):
-    def __init__(self, attachment):
-        self.attachment = attachment
-
-    @property
-    def filename(self):
-        pass
-
-    @property
-    def type(self):
-        pass
-
-    @property
-    def file(self):
-        pass

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c3ab8456/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index b4e64c0..9d7837d 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -36,10 +36,9 @@ class TestGoogleCodeProjectExtractor(TestCase):
         self._p_soup.stop()
 
     def test_init(self):
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
 
         self.urlopen.assert_called_once_with('http://code.google.com/p/my-project/')
-        self.assertEqual(extractor.project, self.project)
         self.soup.assert_called_once_with(self.urlopen.return_value)
         self.assertEqual(extractor.page, self.soup.return_value)
 
@@ -56,10 +55,10 @@ class TestGoogleCodeProjectExtractor(TestCase):
                 'http://code.google.com/p/my-project/')
 
     def test_get_short_description(self):
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
         extractor.page.find.return_value.string = 'My Super Project'
 
-        extractor.get_short_description()
+        extractor.get_short_description(self.project)
 
         extractor.page.find.assert_called_once_with(itemprop='description')
         self.assertEqual(self.project.short_description, 'My Super Project')
@@ -68,11 +67,11 @@ class TestGoogleCodeProjectExtractor(TestCase):
     @mock.patch.object(google, 'M')
     def test_get_icon(self, M, StringIO):
         self.urlopen.return_value.info.return_value = {'content-type': 'image/png'}
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
         extractor.page.find.return_value.attrMap = {'src': 'http://example.com/foo/bar/my-logo.png'}
         self.urlopen.reset_mock()
 
-        extractor.get_icon()
+        extractor.get_icon(self.project)
 
         extractor.page.find.assert_called_once_with(itemprop='image')
         self.urlopen.assert_called_once_with('http://example.com/foo/bar/my-logo.png')
@@ -86,11 +85,11 @@ class TestGoogleCodeProjectExtractor(TestCase):
     @mock.patch.object(google, 'M')
     def test_get_license(self, M):
         self.project.trove_license = []
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
         extractor.page.find.return_value.findNext.return_value.find.return_value.string = '  New BSD License  '
         trove = M.TroveCategory.query.get.return_value
 
-        extractor.get_license()
+        extractor.get_license(self.project)
 
         extractor.page.find.assert_called_once_with(text='Code license')
         extractor.page.find.return_value.findNext.assert_called_once_with()
@@ -100,13 +99,13 @@ class TestGoogleCodeProjectExtractor(TestCase):
 
         M.TroveCategory.query.get.reset_mock()
         extractor.page.find.return_value.findNext.return_value.find.return_value.string = 'non-existant license'
-        extractor.get_license()
+        extractor.get_license(self.project)
         M.TroveCategory.query.get.assert_called_once_with(fullname='Other/Proprietary License')
 
     def _make_extractor(self, html):
         from BeautifulSoup import BeautifulSoup
         with mock.patch.object(google, 'urllib2'):
-            extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project')
+            extractor = google.GoogleCodeProjectExtractor('my-project')
         extractor.page = BeautifulSoup(html)
         extractor.get_page = lambda pagename: extractor.page
         extractor.url="http://test/source/browse"

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c3ab8456/ForgeImporters/forgeimporters/tests/google/test_tasks.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tasks.py b/ForgeImporters/forgeimporters/tests/google/test_tasks.py
index dc7d936..01bab68 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tasks.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tasks.py
@@ -26,8 +26,8 @@ from ...google import tasks
 def test_import_project_info(c, session, gpe):
     c.project = mock.Mock(name='project')
     tasks.import_project_info('my-project')
-    gpe.assert_called_once_with(c.project, 'my-project', 'project_info')
-    gpe.return_value.get_short_description.assert_called_once_with()
-    gpe.return_value.get_icon.assert_called_once_with()
-    gpe.return_value.get_license.assert_called_once_with()
+    gpe.assert_called_once_with('my-project', 'project_info')
+    gpe.return_value.get_short_description.assert_called_once_with(c.project)
+    gpe.return_value.get_icon.assert_called_once_with(c.project)
+    gpe.return_value.get_license.assert_called_once_with(c.project)
     session.flush_all.assert_called_once_with()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c3ab8456/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index e49f279..62493bd 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from datetime import datetime
 from operator import itemgetter
 from unittest import TestCase
 import mock
@@ -24,10 +25,11 @@ from ...google import tracker
 
 class TestTrackerImporter(TestCase):
     @mock.patch.object(tracker, 'c')
+    @mock.patch.object(tracker, 'ThreadLocalORMSession')
     @mock.patch.object(tracker, 'session')
     @mock.patch.object(tracker, 'TM')
-    @mock.patch.object(tracker, 'GDataAPIExtractor')
-    def test_import_tool(self, gdata, TM, session, c):
+    @mock.patch.object(tracker, 'GoogleCodeProjectExtractor')
+    def test_import_tool(self, gpe, TM, session, tlos, c):
         importer = tracker.GoogleCodeTrackerImporter()
         importer.process_fields = mock.Mock()
         importer.process_labels = mock.Mock()
@@ -35,16 +37,14 @@ class TestTrackerImporter(TestCase):
         importer.postprocess_custom_fields = mock.Mock()
         project, user = mock.Mock(), mock.Mock()
         app = project.install_app.return_value
-        extractor = gdata.return_value
-        issues = extractor.iter_issues.return_value = [mock.Mock(), mock.Mock()]
+        issues = gpe.iter_issues.return_value = [mock.Mock(), mock.Mock()]
         tickets = TM.Ticket.new.side_effect = [mock.Mock(), mock.Mock()]
-        comments = extractor.iter_comments.side_effect = [mock.Mock(), mock.Mock()]
 
         importer.import_tool(project, user, project_name='project_name',
                 mount_point='mount_point', mount_label='mount_label')
 
-        project.install_app.assert_called_once_with('tracker', 'mount_point', 'mount_label')
-        gdata.assert_called_once_with('project_name')
+        project.install_app.assert_called_once_with('tickets', 'mount_point', 'mount_label')
+        gpe.iter_issues.assert_called_once_with('project_name')
         self.assertEqual(importer.process_fields.call_args_list, [
                 mock.call(tickets[0], issues[0]),
                 mock.call(tickets[1], issues[1]),
@@ -54,26 +54,16 @@ class TestTrackerImporter(TestCase):
                 mock.call(tickets[1], issues[1]),
             ])
         self.assertEqual(importer.process_comments.call_args_list, [
-                mock.call(tickets[0], comments[0]),
-                mock.call(tickets[1], comments[1]),
+                mock.call(tickets[0], issues[0]),
+                mock.call(tickets[1], issues[1]),
             ])
-        self.assertEqual(extractor.iter_comments.call_args_list, [
-                mock.call(issues[0]),
-                mock.call(issues[1]),
-            ])
-        self.assertEqual(session.call_args_list, [
-                mock.call(tickets[0]),
-                mock.call(tickets[0]),
-                mock.call(tickets[1]),
-                mock.call(tickets[1]),
-                mock.call(app),
-                mock.call(app.globals),
+        self.assertEqual(tlos.flush_all.call_args_list, [
+                mock.call(),
+                mock.call(),
             ])
         self.assertEqual(session.return_value.flush.call_args_list, [
                 mock.call(tickets[0]),
                 mock.call(tickets[1]),
-                mock.call(app),
-                mock.call(app.globals),
             ])
         self.assertEqual(session.return_value.expunge.call_args_list, [
                 mock.call(tickets[0]),
@@ -119,30 +109,37 @@ class TestTrackerImporter(TestCase):
 
     def test_process_fields(self):
         ticket = mock.Mock()
+        def _user(l):
+            u = mock.Mock()
+            u.name = '%sname' % l
+            u.link = '%slink' % l
+            return u
         issue = mock.Mock(
-                summary='summary',
-                description='description',
-                status='status',
-                created_date='created_date',
-                mod_date='mod_date',
+                get_issue_summary=lambda:'summary',
+                get_issue_description=lambda:'description',
+                get_issue_status=lambda:'status',
+                get_issue_created_date=lambda:'created_date',
+                get_issue_mod_date=lambda:'mod_date',
+                get_issue_creator=lambda:_user('c'),
+                get_issue_owner=lambda:_user('o'),
             )
         importer = tracker.GoogleCodeTrackerImporter()
         with mock.patch.object(tracker, 'datetime') as dt:
             dt.strptime.side_effect = lambda s,f: s
             importer.process_fields(ticket, issue)
             self.assertEqual(ticket.summary, 'summary')
-            self.assertEqual(ticket.description, 'description')
+            self.assertEqual(ticket.description, '*Originally created by:* [cname](clink)\n*Originally owned by:* [oname](olink)\n\ndescription')
             self.assertEqual(ticket.status, 'status')
             self.assertEqual(ticket.created_date, 'created_date')
             self.assertEqual(ticket.mod_date, 'mod_date')
             self.assertEqual(dt.strptime.call_args_list, [
-                    mock.call('created_date', ''),
-                    mock.call('mod_date', ''),
+                    mock.call('created_date', '%c'),
+                    mock.call('mod_date', '%c'),
                 ])
 
     def test_process_labels(self):
         ticket = mock.Mock(custom_fields={}, labels=[])
-        issue = mock.Mock(labels=['Foo-Bar', 'Baz', 'Foo-Qux'])
+        issue = mock.Mock(get_issue_labels=lambda:['Foo-Bar', 'Baz', 'Foo-Qux'])
         importer = tracker.GoogleCodeTrackerImporter()
         importer.custom_field = mock.Mock(side_effect=lambda n: {'name': '_%s' % n.lower(), 'options': set()})
         importer.process_labels(ticket, issue)
@@ -156,40 +153,49 @@ class TestTrackerImporter(TestCase):
             a.link = 'author%s_link' % n
             return a
         ticket = mock.Mock()
-        comments = [
+        issue = mock.Mock()
+        comments = issue.iter_comments.return_value = [
                 mock.Mock(
                     author=_author(1),
-                    text='text1',
+                    body='text1',
                     attachments='attachments1',
+                    created_date='Mon Jul 15 00:00:00 2013',
                 ),
                 mock.Mock(
                     author=_author(2),
-                    text='text2',
+                    body='text2',
                     attachments='attachments2',
+                    created_date='Mon Jul 16 00:00:00 2013',
                 ),
             ]
-        comments[0].updates.items.return_value = [('Foo', 'Bar'), ('Baz', 'Qux')]
+        comments[0].updates.items.return_value = [('Foo:', 'Bar'), ('Baz:', 'Qux')]
         comments[1].updates.items.return_value = []
+        posts = ticket.discussion_thread.add_post.side_effect = [
+                mock.Mock(),
+                mock.Mock(),
+            ]
         importer = tracker.GoogleCodeTrackerImporter()
-        importer.process_comments(ticket, comments)
-        self.assertEqual(ticket.thread.add_post.call_args_list[0], mock.call(
-                text='Originally posted by: [author1](author1_link)\n'
+        importer.process_comments(ticket, issue)
+        self.assertEqual(ticket.discussion_thread.add_post.call_args_list[0], mock.call(
+                text='*Originally posted by:* [author1](author1_link)\n'
                 '\n'
                 'text1\n'
                 '\n'
-                '*Foo*: Bar\n'
-                '*Baz*: Qux'
+                '**Foo:** Bar\n'
+                '**Baz:** Qux'
             ))
-        self.assertEqual(ticket.thread.add_post.call_args_list[1], mock.call(
-                text='Originally posted by: [author2](author2_link)\n'
+        self.assertEqual(posts[0].created_date, datetime(2013, 7, 15))
+        self.assertEqual(posts[0].timestamp, datetime(2013, 7, 15))
+        posts[0].add_multiple_attachments.assert_called_once_with('attachments1')
+        self.assertEqual(ticket.discussion_thread.add_post.call_args_list[1], mock.call(
+                text='*Originally posted by:* [author2](author2_link)\n'
                 '\n'
                 'text2\n'
                 '\n'
             ))
-        self.assertEqual(ticket.thread.add_post.return_value.add_multiple_attachments.call_args_list, [
-                mock.call('attachments1'),
-                mock.call('attachments2'),
-            ])
+        self.assertEqual(posts[1].created_date, datetime(2013, 7, 16))
+        self.assertEqual(posts[1].timestamp, datetime(2013, 7, 16))
+        posts[1].add_multiple_attachments.assert_called_once_with('attachments2')
 
     @mock.patch.object(tracker, 'c')
     def test_postprocess_custom_fields(self, c):


[25/25] git commit: [#6464] Refactored plain2markdown tests

Posted by jo...@apache.org.
[#6464] Refactored plain2markdown tests

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/778cece0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/778cece0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/778cece0

Branch: refs/heads/cj/6464
Commit: 778cece02bdc4c0bdde10cb6277e050ddeff3950
Parents: 2844182
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Wed Aug 14 18:47:04 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 14 18:47:04 2013 +0000

----------------------------------------------------------------------
 Allura/allura/tests/decorators.py               | 11 +++
 Allura/allura/tests/test_helpers.py             | 85 ++++++++++++++++++++
 ForgeBlog/forgeblog/tests/test_commands.py      | 42 ----------
 .../forgeimporters/google/__init__.py           |  2 +-
 ForgeImporters/forgeimporters/google/tracker.py |  2 +-
 .../tests/google/functional/test_tracker.py     | 18 +----
 6 files changed, 101 insertions(+), 59 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/778cece0/Allura/allura/tests/decorators.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/decorators.py b/Allura/allura/tests/decorators.py
index 745b008..8c9e4c9 100644
--- a/Allura/allura/tests/decorators.py
+++ b/Allura/allura/tests/decorators.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+import sys
 from functools import wraps
 import contextlib
 
@@ -104,3 +105,13 @@ class raises(object):
                 return False
         else:
             raise AssertionError('Did not raise %s' % self.ExcType)
+
+
+def without_module(*module_names):
+    def _without_module(func):
+        @wraps(func)
+        def wrapped(*a, **kw):
+            with patch.dict(sys.modules, {m: None for m in module_names}):
+                return func(*a, **kw)
+        return wrapped
+    return _without_module

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/778cece0/Allura/allura/tests/test_helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 42fb963..26f5948 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -23,6 +23,8 @@ from mock import Mock, patch
 
 from pylons import tmpl_context as c
 from nose.tools import eq_, assert_equals
+from IPython.testing.decorators import skipif, module_not_available
+from datadiff import tools as dd
 
 from allura import model as M
 from allura.lib import helpers as h
@@ -254,3 +256,86 @@ def test_datetimeformat():
 def test_split_select_field_options():
     assert_equals(h.split_select_field_options('"test message" test2'), ['test message', 'test2'])
     assert_equals(h.split_select_field_options('"test message test2'), ['test', 'message', 'test2'])
+
+
+@skipif(module_not_available('html2text'))
+def test_plain2markdown_with_html2text():
+    """Test plain2markdown using html2text to escape markdown, if available."""
+    text = '''paragraph
+
+    4 spaces before this
+
+    *blah*
+
+here's a <tag> that should be <b>preserved</b>
+Literal &gt; &Ograve; &frac14; &amp; &#38; &#x123F;
+M & Ms - doesn't get escaped
+http://blah.com/?x=y&a=b - not escaped either
+'''
+
+    expected = '''paragraph
+
+4 spaces before this
+
+\*blah\*
+
+here's a &lt;tag&gt; that should be &lt;b&gt;preserved&lt;/b&gt;
+Literal &amp;gt; &amp;Ograve; &amp;frac14; &amp;amp; &amp;\#38; &amp;\#x123F;
+M & Ms - doesn't get escaped
+http://blah.com/?x=y&a=b - not escaped either
+'''
+
+    assert_equals(h.plain2markdown(text), expected)
+
+    assert_equals(h.plain2markdown('a foo  bar\n\n    code here?', preserve_multiple_spaces=True),
+                'a foo&nbsp; bar\n\n&nbsp;&nbsp;&nbsp; code here?')
+
+    assert_equals(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=True),
+                 '&nbsp;&nbsp;&nbsp; tab before \(stuff\)')
+
+    assert_equals(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=False),
+                 'tab before \(stuff\)')
+
+@td.without_module('html2text')
+def test_plain2markdown():
+    """Test plain2markdown using fallback regexp to escape markdown.
+
+    Potentially MD-special characters are aggresively escaped, as without
+    knowledge of the MD parsing rules it's better to be excessive but safe.
+    """
+    text = '''paragraph
+
+    4 spaces before this
+
+    *blah*
+
+here's a <tag> that should be <b>preserved</b>
+Literal &gt; &Ograve; &frac14; &amp; &#38; &#x123F;
+M & Ms - amp doesn't get escaped
+http://blah.com/?x=y&a=b - not escaped either
+back\\-slash escaped
+'''
+
+    expected = '''paragraph
+
+4 spaces before this
+
+\*blah\*
+
+here's a &lt;tag&gt; that should be &lt;b&gt;preserved&lt;/b&gt;
+Literal &amp;gt; &amp;Ograve; &amp;frac14; &amp;amp; &amp;\#38; &amp;\#x123F;
+M & Ms \- amp doesn't get escaped
+http://blah\.com/?x=y&a=b \- not escaped either
+back\\\\\-slash escaped
+'''
+
+    dd.assert_equal(h.plain2markdown(text), expected)
+
+    dd.assert_equal(h.plain2markdown('a foo  bar\n\n    code here?', preserve_multiple_spaces=True),
+                'a foo&nbsp; bar\n\n&nbsp;&nbsp;&nbsp; code here?')
+
+    dd.assert_equal(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=True),
+                 '&nbsp;&nbsp;&nbsp; tab before \(stuff\)')
+
+    dd.assert_equal(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=False),
+                 'tab before \(stuff\)')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/778cece0/ForgeBlog/forgeblog/tests/test_commands.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/tests/test_commands.py b/ForgeBlog/forgeblog/tests/test_commands.py
index 472efda..0b2804a 100644
--- a/ForgeBlog/forgeblog/tests/test_commands.py
+++ b/ForgeBlog/forgeblog/tests/test_commands.py
@@ -168,45 +168,3 @@ def test_plaintext_preprocessor_wrapped():
         '<p>#foo bar <a class="" href="../baz">baz</a> foo bar </p>\n'
         '<p>#foo bar <a class="" href="../baz"> baz </a></p></div>'
     )
-
-@skipif(module_not_available('html2text'))
-def test_plain2markdown():
-    text = '''paragraph
-
-    4 spaces before this
-
-    *blah*
-
-here's a <tag> that should be <b>preserved</b>
-Literal &gt; &Ograve; &frac14; &amp; &#38; &#x123F;
-M & Ms - doesn't get escaped
-http://blah.com/?x=y&a=b - not escaped either
-'''
-
-    expected = '''paragraph
-
-4 spaces before this
-
-\*blah\*
-
-here's a &lt;tag&gt; that should be &lt;b&gt;preserved&lt;/b&gt;
-Literal &amp;gt; &amp;Ograve; &amp;frac14; &amp;amp; &amp;\#38; &amp;\#x123F;
-M & Ms - doesn't get escaped
-http://blah.com/?x=y&a=b - not escaped either
-'''
-    # note: the \# isn't necessary it could be just # but that's the way
-    # html2text escapes all #s currently.  The extra escaping of \# ends up
-    # being ok though when rendered
-
-    from forgeblog.command import rssfeeds
-
-    assert_equal(rssfeeds.plain2markdown(text), expected)
-
-    assert_equal(rssfeeds.plain2markdown('a foo  bar\n\n    code here?', preserve_multiple_spaces=True),
-                'a foo&nbsp; bar\n\n&nbsp;&nbsp;&nbsp; code here?')
-
-    assert_equal(rssfeeds.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=True),
-                 '&nbsp;&nbsp;&nbsp; tab before \(stuff\)')
-
-    assert_equal(rssfeeds.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=False),
-                 'tab before \(stuff\)')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/778cece0/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 1015905..97b29b6 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -269,7 +269,7 @@ class Comment(object):
                 u'{updates}'
             ).format(
                 author=self.author,
-                body=h.plain2markdown(self.body, True),
+                body=h.plain2markdown(self.body, preserve_multiple_spaces=True),
                 updates='\n'.join(
                         '**%s** %s' % (k,v)
                         for k,v in self.updates.items()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/778cece0/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index fefd695..3257c5a 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -96,7 +96,7 @@ class GoogleCodeTrackerImporter(ToolImporter):
                 u'{body}').format(
                     creator=issue.get_issue_creator(),
                     owner=owner_line,
-                    body=h.plain2markdown(issue.get_issue_description(), True),
+                    body=h.plain2markdown(issue.get_issue_description(), preserve_multiple_spaces=True),
                 )
         ticket.add_multiple_attachments(issue.get_issue_attachments())
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/778cece0/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
index 206ac7c..7d67255 100644
--- a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
@@ -27,25 +27,13 @@ from pylons import tmpl_context as c
 from IPython.testing.decorators import module_not_available, skipif
 
 from alluratest.controller import setup_basic_test
+from allura.tests.decorators import without_module
 from allura import model as M
 from forgetracker import model as TM
 from .... import google
 from ....google import tracker
 
 
-def without_html2text(func):
-    @wraps(func)
-    def wrapped(*args, **kw):
-        try:
-            import html2text
-        except ImportError:
-            return func(*args, **kw)
-        else:
-            with mock.patch.object(html2text, 'escape_md_section') as ems:
-                ems.side_effect = ImportError
-                return func(*args, **kw)
-    return wrapped
-
 class TestGCTrackerImporter(TestCase):
     def _make_extractor(self, html):
         with mock.patch.object(google, 'urllib2') as urllib2:
@@ -85,7 +73,7 @@ class TestGCTrackerImporter(TestCase):
         self.assertEqual(ticket.milestone, '')
         self.assertEqual(ticket.custom_fields, {})
 
-    @without_html2text
+    @without_module('html2text')
     def test_issue_basic_fields(self):
         anon = M.User.anonymous()
         ticket = self._make_ticket(self.test_issue)
@@ -162,7 +150,7 @@ class TestGCTrackerImporter(TestCase):
                 ('at2.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000001&name=at2.txt&token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255'),
             )
 
-    @without_html2text
+    @without_module('html2text')
     def test_comments(self):
         anon = M.User.anonymous()
         ticket = self._make_ticket(self.test_issue)


[18/25] git commit: [#6464] Added skip_mod_date to GC tracker importer

Posted by jo...@apache.org.
[#6464] Added skip_mod_date to GC tracker importer

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/ed86e131
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/ed86e131
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/ed86e131

Branch: refs/heads/cj/6464
Commit: ed86e1312da0044ceb3a109609f6a49425648519
Parents: 8efe97c
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Thu Aug 8 21:10:24 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 14 15:41:13 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/tracker.py | 23 ++++++++++++--------
 1 file changed, 14 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ed86e131/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index a997429..bd3a57f 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -21,6 +21,7 @@ from datetime import datetime
 from pylons import tmpl_context as c
 from ming.orm import session, ThreadLocalORMSession
 
+from allura import model as M
 from allura.lib import helpers as h
 
 from forgetracker.tracker_main import ForgeTrackerApp
@@ -48,15 +49,19 @@ class GoogleCodeTrackerImporter(ToolImporter):
         c.app.globals.open_status_names = 'New Accepted Started'
         c.app.globals.closed_status_names = 'Fixed Verified Invalid Duplicate WontFix Done'
         self.custom_fields = {}
-        for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
-            ticket = TM.Ticket.new()
-            self.process_fields(ticket, issue)
-            self.process_labels(ticket, issue)
-            self.process_comments(ticket, issue)
-            session(ticket).flush(ticket)
-            session(ticket).expunge(ticket)
-        self.postprocess_custom_fields()
-        ThreadLocalORMSession.flush_all()
+        try:
+            M.session.artifact_orm_session._get().skip_mod_date = True
+            for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
+                ticket = TM.Ticket.new()
+                self.process_fields(ticket, issue)
+                self.process_labels(ticket, issue)
+                self.process_comments(ticket, issue)
+                session(ticket).flush(ticket)
+                session(ticket).expunge(ticket)
+            self.postprocess_custom_fields()
+            ThreadLocalORMSession.flush_all()
+        finally:
+            M.session.artifact_orm_session._get().skip_mod_date = False
 
     def custom_field(self, name):
         if name not in self.custom_fields:


[21/25] git commit: [#6464] Fixed test failing due to refactor

Posted by jo...@apache.org.
[#6464] Fixed test failing due to refactor

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/980111a9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/980111a9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/980111a9

Branch: refs/heads/cj/6464
Commit: 980111a9492d49f82fcada38aade28412deaa8e1
Parents: 4e0956b
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue Aug 13 14:07:38 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 14 15:41:13 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/tests/google/test_tracker.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/980111a9/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index 3efd97d..70ea3ad 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -212,8 +212,8 @@ class TestTrackerImporter(TestCase):
                     'options': set(['foo', 'bar']),
                 },
             }
-        importer.postprocess_custom_fields()
-        self.assertEqual(sorted(c.app.globals.custom_fields, key=itemgetter('name')), [
+        custom_fields = importer.postprocess_custom_fields()
+        self.assertItemsEqual(custom_fields, [
                 {
                     'name': '_foo',
                     'type': 'string',


[19/25] git commit: [#6464] Fixed attribute error on missing / empty GC issue fields and added tests

Posted by jo...@apache.org.
[#6464] Fixed attribute error on missing / empty GC issue fields and added tests

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/50faffb7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/50faffb7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/50faffb7

Branch: refs/heads/cj/6464
Commit: 50faffb74536606c6764a6e08efa3436f9e5e990
Parents: ed86e13
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri Aug 9 14:35:59 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 14 15:41:13 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/google/__init__.py           |  14 +-
 .../tests/data/google/empty-issue.html          | 306 ++++++++++++
 .../tests/data/google/test-issue.html           | 488 +++++++++++++++++++
 .../tests/google/test_extractor.py              |  58 +++
 4 files changed, 863 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/50faffb7/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 5f09cb9..9f5324a 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -186,7 +186,7 @@ class GoogleCodeProjectExtractor(object):
         return self.page.find(id='issueheader').findAll('td', limit=2)[1].span.string.strip()
 
     def get_issue_description(self):
-        return _as_text(self.page.find(id='hc0').pre)
+        return _as_text(self.page.find(id='hc0').pre).strip()
 
     def get_issue_created_date(self):
         return self.page.find(id='hc0').find('span', 'date').get('title')
@@ -200,10 +200,18 @@ class GoogleCodeProjectExtractor(object):
         return UserLink(a)
 
     def get_issue_status(self):
-        return self.page.find(id='issuemeta').find('th', text=re.compile('Status:')).findNext().span.string.strip()
+        tag = self.page.find(id='issuemeta').find('th', text=re.compile('Status:')).findNext().span
+        if tag:
+            return tag.string.strip()
+        else:
+            return ''
 
     def get_issue_owner(self):
-        return UserLink(self.page.find(id='issuemeta').find('th', text=re.compile('Owner:')).findNext().a)
+        tag = self.page.find(id='issuemeta').find('th', text=re.compile('Owner:')).findNext().a
+        if tag:
+            return UserLink(tag)
+        else:
+            return None
 
     def get_issue_labels(self):
         label_nodes = self.page.find(id='issuemeta').findAll('a', 'label')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/50faffb7/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html b/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
new file mode 100644
index 0000000..a8fb1d5
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
@@ -0,0 +1,306 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+<meta name="ROBOTS" content="NOARCHIVE" />
+<link rel="icon" type="image/vnd.microsoft.icon" href="http://www.gstatic.com/codesite/ph/images/phosting.ico" />
+<script type="text/javascript">
+
+
+
+
+ var codesite_token = null;
+
+
+ var CS_env = {"assetHostPath":"http://www.gstatic.com/codesite/ph","projectHomeUrl":"/p/allura-google-importer","relativeBaseUrl":"","domainName":null,"projectName":"allura-google-importer","loggedInUserEmail":null,"profileUrl":null,"token":null,"assetVersionPath":"http://www.gstatic.com/codesite/ph/3783617020303179221"};
+ var _gaq = _gaq || [];
+ _gaq.push(
+ ['siteTracker._setAccount', 'UA-18071-1'],
+ ['siteTracker._trackPageview']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
+ })();
+
+ </script>
+<title>Issue 5 -
+ allura-google-importer -
+
+ Empty Issue -
+ Import Google Code projects to an Allura forge - Google Project Hosting
+ </title>
+<link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/core.css" />
+<link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/ph_detail.css" />
+<!--[if IE]>
+ <link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/d_ie.css" >
+<![endif]-->
+<style type="text/css">
+ .menuIcon.off { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 -42px }
+ .menuIcon.on { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 -28px }
+ .menuIcon.down { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 0; }
+
+
+ .attachments { width:33%; border-top:2px solid #999; padding-top: 3px; margin-left: .7em;}
+ .attachments table { margin-bottom: 0.75em; }
+ .attachments table tr td { padding: 0; margin: 0; font-size: 95%; }
+ .preview { border: 2px solid #c3d9ff; padding: 1px; }
+ .preview:hover { border: 2px solid blue; }
+ .label { white-space: nowrap; }
+ .derived { font-style:italic }
+ .cursor_on .author {
+ background: url(http://www.gstatic.com/codesite/ph/images/show-arrow.gif) no-repeat 2px;
+ }
+ .hiddenform {
+ display: none;
+ }
+
+
+ </style>
+</head>
+<body class="t3">
+<script type="text/javascript">
+ window.___gcfg = {lang: 'en'};
+ (function()
+ {var po = document.createElement("script");
+ po.type = "text/javascript"; po.async = true;po.src = "https://apis.google.com/js/plusone.js";
+ var s = document.getElementsByTagName("script")[0];
+ s.parentNode.insertBefore(po, s);
+ })();
+</script>
+<div class="headbg">
+<div id="gaia">
+<span>
+<a href="#" id="projects-dropdown" onclick="return false;"><u>My favorites</u> <small>&#9660;</small></a>
+ | <a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5" onclick="_CS_click('/gb/ph/signin');"><u>Sign in</u></a>
+</span>
+</div>
+<div class="gbh" style="left: 0pt;"></div>
+<div class="gbh" style="right: 0pt;"></div>
+<div style="height: 1px"></div>
+<!--[if lte IE 7]>
+<div style="text-align:center;">
+Your version of Internet Explorer is not supported. Try a browser that
+contributes to open source, such as <a href="http://www.firefox.com">Firefox</a>,
+<a href="http://www.google.com/chrome">Google Chrome</a>, or
+<a href="http://code.google.com/chrome/chromeframe/">Google Chrome Frame</a>.
+</div>
+<![endif]-->
+<table style="padding:0px; margin: 0px 0px 10px 0px; width:100%" cellpadding="0" cellspacing="0" itemscope="itemscope" itemtype="http://schema.org/CreativeWork">
+<tr style="height: 58px;">
+<td id="plogo">
+<link itemprop="url" href="/p/allura-google-importer" />
+<a href="/p/allura-google-importer/">
+<img src="/p/allura-google-importer/logo?cct=1374769571" alt="Logo" itemprop="image" />
+</a>
+</td>
+<td style="padding-left: 0.5em">
+<div id="pname">
+<a href="/p/allura-google-importer/"><span itemprop="name">allura-google-importer</span></a>
+</div>
+<div id="psum">
+<a id="project_summary_link" href="/p/allura-google-importer/"><span itemprop="description">Import Google Code projects to an Allura forge</span></a>
+</div>
+</td>
+<td style="white-space:nowrap;text-align:right; vertical-align:bottom;">
+<form action="/hosting/search">
+<input size="30" name="q" value="" type="text" />
+<input type="submit" name="projectsearch" value="Search projects" />
+</form>
+</td></tr>
+</table>
+</div>
+<div id="mt" class="gtb">
+<a href="/p/allura-google-importer/" class="tab ">Project&nbsp;Home</a>
+<a href="/p/allura-google-importer/wiki/TestPage?tm=6" class="tab ">Wiki</a>
+<a href="/p/allura-google-importer/issues/list" class="tab active">Issues</a>
+<a href="/p/allura-google-importer/source/checkout" class="tab ">Source</a>
+<div class="gtbc"></div>
+</div>
+<table cellspacing="0" cellpadding="0" width="100%" align="center" border="0" class="st">
+<tr>
+<td class="subt">
+<div class="issueDetail">
+<div class="isf">
+<span class="inIssueEntry">
+<a class="buttonify" href="entry" onclick="return _newIssuePrompt();">New issue</a>
+</span> &nbsp;
+
+ <span class="inIssueList">
+<span>Search</span>
+</span><form action="list" method="GET" style="display:inline">
+<select id="can" name="can">
+<option disabled="disabled">Search within:</option>
+<option value="1">&nbsp;All issues</option>
+<option value="2" selected="selected">&nbsp;Open issues</option>
+<option value="6">&nbsp;New issues</option>
+<option value="7">&nbsp;Issues to verify</option>
+</select>
+<span>for</span>
+<span id="qq"><input type="text" size="38" id="searchq" name="q" value="" autocomplete="off" onkeydown="_blurOnEsc(event)" /></span>
+<span id="search_colspec"><input type="hidden" name="colspec" value="ID Type Status Priority Milestone Owner Summary" /></span>
+<input type="hidden" name="cells" value="tiles" />
+<input type="submit" value="Search" />
+</form>
+ &nbsp;
+ <span class="inIssueAdvSearch">
+<a href="advsearch">Advanced search</a>
+</span> &nbsp;
+ <span class="inIssueSearchTips">
+<a href="searchtips">Search tips</a>
+</span> &nbsp;
+ <span class="inIssueSubscriptions">
+<a href="/p/allura-google-importer/issues/subscriptions">Subscriptions</a>
+</span>
+</div>
+</div>
+</td>
+<td align="right" valign="top" class="bevel-right"></td>
+</tr>
+</table>
+<script type="text/javascript">
+ var cancelBubble = false;
+ function _go(url) { document.location = url; }
+</script>
+<div id="maincol">
+<div id="color_control" class="">
+<div id="issueheader">
+<table cellpadding="0" cellspacing="0" width="100%"><tbody>
+<tr>
+<td class="vt h3" nowrap="nowrap" style="padding:0 5px">
+
+
+ Issue <a href="detail?id=5">5</a>:
+ </td>
+<td width="90%" class="vt">
+<span class="h3">Empty Issue</span>
+</td>
+<td>
+<div class="pagination">
+<a href="../../allura-google-importer/issues/detail?id=4" title="Prev">&lsaquo; Prev</a>
+ 5 of 5
+
+ </div>
+</td>
+</tr>
+<tr>
+<td></td>
+<td nowrap="nowrap">
+
+
+ 1 person starred this issue and may be notified of changes.
+
+
+
+ </td>
+<td align="center" nowrap="nowrap">
+<a href="http://code.google.com/p/allura-google-importer/issues/list?cursor=allura-google-importer%3A5">Back to list</a>
+</td>
+</tr>
+</tbody></table>
+</div>
+<table width="100%" cellpadding="0" cellspacing="0" border="0" class="issuepage" id="meta-container">
+<tbody class="collapse">
+<tr>
+<td id="issuemeta">
+<div id="meta-float">
+<table cellspacing="0" cellpadding="0">
+<tr><th align="left">Status:&nbsp;</th>
+<td width="100%">
+
+
+ ----
+
+
+ </td>
+</tr>
+<tr><th align="left">Owner:&nbsp;</th><td>
+
+
+ ----
+
+
+ </td>
+</tr>
+<tr><td colspan="2">
+</td></tr>
+</table>
+<div class="rel_issues">
+</div>
+<br /><br />
+<div style="white-space:nowrap"><a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5">Sign in</a> to add a comment</div>
+</div>&nbsp;
+ </td>
+<td class="vt issuedescription" width="100%" id="cursorarea">
+<div class="cursor_off vt issuedescription" id="hc0">
+<div class="author">
+<span class="role_label">Project Member</span>
+ Reported by
+
+
+ <a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a>,
+ <span class="date" title="Thu Aug  8 14:56:23 2013">Today (15 minutes ago)</span>
+</div>
+<pre>
+Empty
+</pre>
+</div>
+</td>
+</tr>
+<tr>
+<td></td>
+<td class="vt issuecomment">
+<span class="indicator">&#9658;</span> <a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5">Sign in</a> to add a comment
+ </td>
+</tr>
+</tbody>
+</table>
+<br />
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/dit_scripts.js"></script>
+</div>
+<form name="delcom" action="delComment.do?q=&amp;can=2&amp;groupby=&amp;sort=&amp;colspec=ID+Type+Status+Priority+Milestone+Owner+Summary" method="POST">
+<input type="hidden" name="sequence_num" value="" />
+<input type="hidden" name="mode" value="" />
+<input type="hidden" name="id" value="5" />
+<input type="hidden" name="token" value="" />
+</form>
+<div id="helparea"></div>
+<script type="text/javascript">
+ _onload();
+ function delComment(sequence_num, delete_mode) {
+ var f = document.forms["delcom"];
+ f.sequence_num.value = sequence_num;
+ f.mode.value = delete_mode;
+
+ f.submit();
+ return false;
+ }
+
+ _floatMetadata();
+</script>
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/kibbles.js"></script>
+<script type="text/javascript">
+ _setupKibblesOnDetailPage(
+ 'http://code.google.com/p/allura-google-importer/issues/list?cursor\x3dallura-google-importer%3A5',
+ '/p/allura-google-importer/issues/entry',
+ '../../allura-google-importer/issues/detail?id\x3d4',
+ '',
+ '', 'allura-google-importer', 5,
+ false, false, codesite_token);
+</script>
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/ph_core.js"></script>
+</div>
+<div id="footer" dir="ltr">
+<div class="text">
+<a href="/projecthosting/terms.html">Terms</a> -
+ <a href="http://www.google.com/privacy.html">Privacy</a> -
+ <a href="/p/support/">Project Hosting Help</a>
+</div>
+</div>
+<div class="hostedBy" style="margin-top: -20px;">
+<span style="vertical-align: top;">Powered by <a href="http://code.google.com/projecthosting/">Google Project Hosting</a></span>
+</div>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/50faffb7/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/data/google/test-issue.html b/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
new file mode 100644
index 0000000..04667ac
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
@@ -0,0 +1,488 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+<meta name="ROBOTS" content="NOARCHIVE" />
+<link rel="icon" type="image/vnd.microsoft.icon" href="http://www.gstatic.com/codesite/ph/images/phosting.ico" />
+<script type="text/javascript">
+
+
+
+
+ var codesite_token = null;
+
+
+ var CS_env = {"loggedInUserEmail":null,"relativeBaseUrl":"","projectHomeUrl":"/p/allura-google-importer","assetVersionPath":"http://www.gstatic.com/codesite/ph/3783617020303179221","assetHostPath":"http://www.gstatic.com/codesite/ph","domainName":null,"projectName":"allura-google-importer","token":null,"profileUrl":null};
+ var _gaq = _gaq || [];
+ _gaq.push(
+ ['siteTracker._setAccount', 'UA-18071-1'],
+ ['siteTracker._trackPageview']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
+ })();
+
+ </script>
+<title>Issue 6 -
+ allura-google-importer -
+
+ Test Issue -
+ Import Google Code projects to an Allura forge - Google Project Hosting
+ </title>
+<link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/core.css" />
+<link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/ph_detail.css" />
+<!--[if IE]>
+ <link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/d_ie.css" >
+<![endif]-->
+<style type="text/css">
+ .menuIcon.off { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 -42px }
+ .menuIcon.on { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 -28px }
+ .menuIcon.down { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 0; }
+
+
+ .attachments { width:33%; border-top:2px solid #999; padding-top: 3px; margin-left: .7em;}
+ .attachments table { margin-bottom: 0.75em; }
+ .attachments table tr td { padding: 0; margin: 0; font-size: 95%; }
+ .preview { border: 2px solid #c3d9ff; padding: 1px; }
+ .preview:hover { border: 2px solid blue; }
+ .label { white-space: nowrap; }
+ .derived { font-style:italic }
+ .cursor_on .author {
+ background: url(http://www.gstatic.com/codesite/ph/images/show-arrow.gif) no-repeat 2px;
+ }
+ .hiddenform {
+ display: none;
+ }
+
+
+ </style>
+</head>
+<body class="t3">
+<script type="text/javascript">
+ window.___gcfg = {lang: 'en'};
+ (function()
+ {var po = document.createElement("script");
+ po.type = "text/javascript"; po.async = true;po.src = "https://apis.google.com/js/plusone.js";
+ var s = document.getElementsByTagName("script")[0];
+ s.parentNode.insertBefore(po, s);
+ })();
+</script>
+<div class="headbg">
+<div id="gaia">
+<span>
+<a href="#" id="projects-dropdown" onclick="return false;"><u>My favorites</u> <small>&#9660;</small></a>
+ | <a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6" onclick="_CS_click('/gb/ph/signin');"><u>Sign in</u></a>
+</span>
+</div>
+<div class="gbh" style="left: 0pt;"></div>
+<div class="gbh" style="right: 0pt;"></div>
+<div style="height: 1px"></div>
+<!--[if lte IE 7]>
+<div style="text-align:center;">
+Your version of Internet Explorer is not supported. Try a browser that
+contributes to open source, such as <a href="http://www.firefox.com">Firefox</a>,
+<a href="http://www.google.com/chrome">Google Chrome</a>, or
+<a href="http://code.google.com/chrome/chromeframe/">Google Chrome Frame</a>.
+</div>
+<![endif]-->
+<table style="padding:0px; margin: 0px 0px 10px 0px; width:100%" cellpadding="0" cellspacing="0" itemscope="itemscope" itemtype="http://schema.org/CreativeWork">
+<tr style="height: 58px;">
+<td id="plogo">
+<link itemprop="url" href="/p/allura-google-importer" />
+<a href="/p/allura-google-importer/">
+<img src="/p/allura-google-importer/logo?cct=1374769571" alt="Logo" itemprop="image" />
+</a>
+</td>
+<td style="padding-left: 0.5em">
+<div id="pname">
+<a href="/p/allura-google-importer/"><span itemprop="name">allura-google-importer</span></a>
+</div>
+<div id="psum">
+<a id="project_summary_link" href="/p/allura-google-importer/"><span itemprop="description">Import Google Code projects to an Allura forge</span></a>
+</div>
+</td>
+<td style="white-space:nowrap;text-align:right; vertical-align:bottom;">
+<form action="/hosting/search">
+<input size="30" name="q" value="" type="text" />
+<input type="submit" name="projectsearch" value="Search projects" />
+</form>
+</td></tr>
+</table>
+</div>
+<div id="mt" class="gtb">
+<a href="/p/allura-google-importer/" class="tab ">Project&nbsp;Home</a>
+<a href="/p/allura-google-importer/wiki/TestPage?tm=6" class="tab ">Wiki</a>
+<a href="/p/allura-google-importer/issues/list" class="tab active">Issues</a>
+<a href="/p/allura-google-importer/source/checkout" class="tab ">Source</a>
+<div class="gtbc"></div>
+</div>
+<table cellspacing="0" cellpadding="0" width="100%" align="center" border="0" class="st">
+<tr>
+<td class="subt">
+<div class="issueDetail">
+<div class="isf">
+<span class="inIssueEntry">
+<a class="buttonify" href="entry" onclick="return _newIssuePrompt();">New issue</a>
+</span> &nbsp;
+
+ <span class="inIssueList">
+<span>Search</span>
+</span><form action="list" method="GET" style="display:inline">
+<select id="can" name="can">
+<option disabled="disabled">Search within:</option>
+<option value="1">&nbsp;All issues</option>
+<option value="2" selected="selected">&nbsp;Open issues</option>
+<option value="6">&nbsp;New issues</option>
+<option value="7">&nbsp;Issues to verify</option>
+</select>
+<span>for</span>
+<span id="qq"><input type="text" size="38" id="searchq" name="q" value="" autocomplete="off" onkeydown="_blurOnEsc(event)" /></span>
+<span id="search_colspec"><input type="hidden" name="colspec" value="ID Type Status Priority Milestone Owner Summary" /></span>
+<input type="hidden" name="cells" value="tiles" />
+<input type="submit" value="Search" />
+</form>
+ &nbsp;
+ <span class="inIssueAdvSearch">
+<a href="advsearch">Advanced search</a>
+</span> &nbsp;
+ <span class="inIssueSearchTips">
+<a href="searchtips">Search tips</a>
+</span> &nbsp;
+ <span class="inIssueSubscriptions">
+<a href="/p/allura-google-importer/issues/subscriptions">Subscriptions</a>
+</span>
+</div>
+</div>
+</td>
+<td align="right" valign="top" class="bevel-right"></td>
+</tr>
+</table>
+<script type="text/javascript">
+ var cancelBubble = false;
+ function _go(url) { document.location = url; }
+</script>
+<div id="maincol">
+<div id="color_control" class="">
+<div id="issueheader">
+<table cellpadding="0" cellspacing="0" width="100%"><tbody>
+<tr>
+<td class="vt h3" nowrap="nowrap" style="padding:0 5px">
+
+
+ Issue <a href="detail?id=6">6</a>:
+ </td>
+<td width="90%" class="vt">
+<span class="h3">Test Issue</span>
+</td>
+<td>
+<div class="pagination">
+<a href="../../allura-google-importer/issues/detail?id=5" title="Prev">&lsaquo; Prev</a>
+ 6 of 6
+
+ </div>
+</td>
+</tr>
+<tr>
+<td></td>
+<td nowrap="nowrap">
+
+
+ 1 person starred this issue and may be notified of changes.
+
+
+
+ </td>
+<td align="center" nowrap="nowrap">
+<a href="http://code.google.com/p/allura-google-importer/issues/list?cursor=allura-google-importer%3A6">Back to list</a>
+</td>
+</tr>
+</tbody></table>
+</div>
+<table width="100%" cellpadding="0" cellspacing="0" border="0" class="issuepage" id="meta-container">
+<tbody class="collapse">
+<tr>
+<td id="issuemeta">
+<div id="meta-float">
+<table cellspacing="0" cellpadding="0">
+<tr><th align="left">Status:&nbsp;</th>
+<td width="100%">
+<span title="Work on this issue has begun">Started</span>
+</td>
+</tr>
+<tr><th align="left">Owner:&nbsp;</th><td>
+<a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a>
+</td>
+</tr>
+<tr><td colspan="2">
+<div style="padding-top:2px">
+<a href="list?q=label:Type-Defect" title="Report of a software defect" class="label"><b>Type-</b>Defect</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:Priority-Medium" title="Normal priority" class="label"><b>Priority-</b>Medium</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:Milestone-Release1.0" title="All essential functionality working" class="label"><b>Milestone-</b>Release1.0</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:OpSys-All" title="Affects all operating systems" class="label"><b>OpSys-</b>All</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:Component-Logic" title="Issue relates to application logic" class="label"><b>Component-</b>Logic</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:Performance" title="Performance issue" class="label">Performance</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:Security" title="Security risk to users" class="label">Security</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:OpSys-Windows" title="Affects Windows users" class="label"><b>OpSys-</b>Windows</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:OpSys-OSX" title="Affects Mac OS X users" class="label"><b>OpSys-</b>OSX</a>
+</div>
+</td></tr>
+</table>
+<div class="rel_issues">
+</div>
+<br /><br />
+<div style="white-space:nowrap"><a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6">Sign in</a> to add a comment</div>
+</div>&nbsp;
+ </td>
+<td class="vt issuedescription" width="100%" id="cursorarea">
+<div class="cursor_off vt issuedescription" id="hc0">
+<div class="author">
+<span class="role_label">Project Member</span>
+ Reported by
+
+
+ <a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a>,
+ <span class="date" title="Thu Aug  8 15:33:52 2013">Today (3 minutes ago)</span>
+</div>
+<pre>
+Test *Issue* for testing
+
+  1. Test List
+  2. Item
+
+**Testing**
+
+ * Test list 2
+ * Item
+
+# Test Section
+
+    p = source.test_issue.post()
+    p.count = p.count *5 #* 6
+
+That's all
+
+
+</pre>
+</div>
+<div class="cursor_off vt issuecomment" id="hc1">
+<div style="float:right; margin-right:.3em; text-align:right">
+<span class="date" title="Thu Aug  8 15:35:15 2013">
+ Today (2 minutes ago)
+ </span>
+</div>
+<span class="author">
+<span class="role_label">Project Member</span>
+<a name="c1" href="/p/allura-google-importer/issues/detail?id=6#c1">#1</a>
+<a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a></span>
+<pre>
+Test *comment* is a comment
+</pre>
+<div class="attachments">
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60001000&amp;name=at2.txt&amp;token=JOSo4duwaN2FCKZrwYOQ-nx9r7U%3A1376001446667">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at2.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=6&amp;aid=60001000&amp;name=at2.txt&amp;token=JOSo4duwaN2FCKZrwYOQ-nx9r7U%3A1376001446667" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60001000&amp;name=at2.txt&amp;token=JOSo4duwaN2FCKZrwYOQ-nx9r7U%3A1376001446667">Download</a>
+</td>
+</tr>
+</table>
+</div>
+<div class="updates">
+<div class="round4"></div>
+<div class="round2"></div>
+<div class="round1"></div>
+<div class="box-inner">
+<b>Status:</b>
+ Started
+
+ <br />
+<b>Labels:</b>
+ -OpSys-Linux OpSys-Windows
+
+ <br />
+</div>
+<div class="round1"></div>
+<div class="round2"></div>
+<div class="round4"></div>
+</div>
+</div>
+<div class="cursor_off vt issuecomment" id="hc2">
+<div style="float:right; margin-right:.3em; text-align:right">
+<span class="date" title="Thu Aug  8 15:35:34 2013">
+ Today (1 minute ago)
+ </span>
+</div>
+<span class="author">
+<span class="role_label">Project Member</span>
+<a name="c2" href="/p/allura-google-importer/issues/detail?id=6#c2">#2</a>
+<a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a></span>
+<pre>
+Another comment
+</pre>
+</div>
+<div class="cursor_off vt issuecomment" id="hc3">
+<div style="float:right; margin-right:.3em; text-align:right">
+<span class="date" title="Thu Aug  8 15:36:39 2013">
+ Today (moments ago)
+ </span>
+</div>
+<span class="author">
+<span class="role_label">Project Member</span>
+<a name="c3" href="/p/allura-google-importer/issues/detail?id=6#c3">#3</a>
+<a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a></span>
+<pre>
+Last comment
+</pre>
+<div class="attachments">
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60003000&amp;name=at4.txt&amp;token=6Ny2zYHmV6b82dqxyoiH6HUYoC4%3A1376001446667">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at4.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=6&amp;aid=60003000&amp;name=at4.txt&amp;token=6Ny2zYHmV6b82dqxyoiH6HUYoC4%3A1376001446667" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60003000&amp;name=at4.txt&amp;token=6Ny2zYHmV6b82dqxyoiH6HUYoC4%3A1376001446667">Download</a>
+</td>
+</tr>
+</table>
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60003001&amp;name=at1.txt&amp;token=NS8aMvWsKzTAPuY2kniJG5aLzPg%3A1376001446667">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at1.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=6&amp;aid=60003001&amp;name=at1.txt&amp;token=NS8aMvWsKzTAPuY2kniJG5aLzPg%3A1376001446667" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60003001&amp;name=at1.txt&amp;token=NS8aMvWsKzTAPuY2kniJG5aLzPg%3A1376001446667">Download</a>
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="cursor_off vt issuecomment" id="hc4">
+<div style="float:right; margin-right:.3em; text-align:right">
+<span class="date" title="Thu Aug  8 15:36:57 2013">
+ Today (moments ago)
+ </span>
+</div>
+<span class="author">
+<span class="role_label">Project Member</span>
+<a name="c4" href="/p/allura-google-importer/issues/detail?id=6#c4">#4</a>
+<a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a></span>
+<pre>
+Oh, I forgot one
+</pre>
+<div class="updates">
+<div class="round4"></div>
+<div class="round2"></div>
+<div class="round1"></div>
+<div class="box-inner">
+<b>Labels:</b>
+ OpSys-OSX
+
+ <br />
+</div>
+<div class="round1"></div>
+<div class="round2"></div>
+<div class="round4"></div>
+</div>
+</div>
+</td>
+</tr>
+<tr>
+<td></td>
+<td class="vt issuecomment">
+<span class="indicator">&#9658;</span> <a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6">Sign in</a> to add a comment
+ </td>
+</tr>
+</tbody>
+</table>
+<br />
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/dit_scripts.js"></script>
+</div>
+<form name="delcom" action="delComment.do?q=&amp;can=2&amp;groupby=&amp;sort=&amp;colspec=ID+Type+Status+Priority+Milestone+Owner+Summary" method="POST">
+<input type="hidden" name="sequence_num" value="" />
+<input type="hidden" name="mode" value="" />
+<input type="hidden" name="id" value="6" />
+<input type="hidden" name="token" value="" />
+</form>
+<div id="helparea"></div>
+<script type="text/javascript">
+ _onload();
+ function delComment(sequence_num, delete_mode) {
+ var f = document.forms["delcom"];
+ f.sequence_num.value = sequence_num;
+ f.mode.value = delete_mode;
+
+ f.submit();
+ return false;
+ }
+
+ _floatMetadata();
+</script>
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/kibbles.js"></script>
+<script type="text/javascript">
+ _setupKibblesOnDetailPage(
+ 'http://code.google.com/p/allura-google-importer/issues/list?cursor\x3dallura-google-importer%3A6',
+ '/p/allura-google-importer/issues/entry',
+ '../../allura-google-importer/issues/detail?id\x3d5',
+ '',
+ '', 'allura-google-importer', 6,
+ false, false, codesite_token);
+</script>
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/ph_core.js"></script>
+</div>
+<div id="footer" dir="ltr">
+<div class="text">
+<a href="/projecthosting/terms.html">Terms</a> -
+ <a href="http://www.google.com/privacy.html">Privacy</a> -
+ <a href="/p/support/">Project Hosting Help</a>
+</div>
+</div>
+<div class="hostedBy" style="margin-top: -20px;">
+<span style="vertical-align: top;">Powered by <a href="http://code.google.com/projecthosting/">Google Project Hosting</a></span>
+</div>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/50faffb7/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index 9d7837d..f6e2b2c 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 from unittest import TestCase
+import pkg_resources
 
 import mock
 
@@ -130,3 +131,60 @@ class TestGoogleCodeProjectExtractor(TestCase):
         with self.assertRaises(Exception) as cm:
             extractor.get_repo_type()
         self.assertEqual(str(cm.exception), "Unknown repo type: cvs")
+
+    def test_empty_issue(self):
+        empty_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/empty-issue.html')).read()
+        gpe = self._make_extractor(empty_issue)
+        self.assertIsNone(gpe.get_issue_owner())
+        self.assertEqual(gpe.get_issue_status(), '')
+        self.assertEqual(gpe.get_issue_attachments(), [])
+        self.assertEqual(list(gpe.iter_comments()), [])
+
+    def test_get_issue_basic_fields(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        self.assertEqual(gpe.get_issue_creator().name, 'john...@gmail.com')
+        self.assertEqual(gpe.get_issue_creator().link, 'http://code.google.com/u/101557263855536553789/')
+        self.assertEqual(gpe.get_issue_owner().name, 'john...@gmail.com')
+        self.assertEqual(gpe.get_issue_owner().link, 'http://code.google.com/u/101557263855536553789/')
+        self.assertEqual(gpe.get_issue_status(), 'Started')
+        self.assertEqual(gpe.get_issue_summary(), 'Test Issue')
+        self.assertEqual(gpe.get_issue_description(),
+                'Test *Issue* for testing\n'
+                '\n'
+                '  1. Test List\n'
+                '  2. Item\n'
+                '\n'
+                '**Testing**\n'
+                '\n'
+                ' * Test list 2\n'
+                ' * Item\n'
+                '\n'
+                '# Test Section\n'
+                '\n'
+                '    p = source.test_issue.post()\n'
+                '    p.count = p.count *5 #* 6\n'
+                '\n'
+                'That\'s all'
+            )
+        self.assertEqual(gpe.get_issue_created_date(), 'Thu Aug  8 15:33:52 2013')
+
+    def test_get_issue_mod_date(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        self.assertEqual(gpe.get_issue_mod_date(), 'Thu Aug  8 15:36:57 2013')
+
+    def test_get_issue_labels(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        self.assertEqual(gpe.get_issue_labels(), [
+                'Type-Defect',
+                'Priority-Medium',
+                'Milestone-Release1.0',
+                'OpSys-All',
+                'Component-Logic',
+                'Performance',
+                'Security',
+                'OpSys-Windows',
+                'OpSys-OSX',
+            ])


[05/25] git commit: [#6554] let ProjectConflict be constructed with a single string, like it did before adding Invalid as a parent

Posted by jo...@apache.org.
[#6554] let ProjectConflict be constructed with a single string, like it did before adding Invalid as a parent


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/d43c2544
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/d43c2544
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/d43c2544

Branch: refs/heads/cj/6464
Commit: d43c25447577c512a991b6c70b703fc8438c4cc3
Parents: 9e3e394
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Mon Aug 12 21:34:43 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Aug 12 21:34:43 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/exceptions.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/d43c2544/Allura/allura/lib/exceptions.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/exceptions.py b/Allura/allura/lib/exceptions.py
index 4c33991..7611dd7 100644
--- a/Allura/allura/lib/exceptions.py
+++ b/Allura/allura/lib/exceptions.py
@@ -18,7 +18,14 @@
 from formencode import Invalid
 
 class ForgeError(Exception): pass
-class ProjectConflict(ForgeError, Invalid): pass
+
+class ProjectConflict(ForgeError, Invalid):
+
+    # support the single string constructor in addition to full set of params that Invalid.__init__ requires
+    def __init__(self, msg, value=None, state=None, error_list=None, error_dict=None):
+        super(ProjectConflict, self).__init__(msg, value, state, error_list, error_dict)
+
+
 class ProjectShortnameInvalid(ForgeError, Invalid): pass
 class ProjectOverlimitError(ForgeError): pass
 class ProjectRatelimitError(ForgeError): pass
@@ -45,4 +52,3 @@ class CompoundError(ForgeError):
                 parts.append('    ' + line)
         parts.append('</%s>\n' % self.__class__.__name__ )
         return ''.join(parts)
-                


[08/25] git commit: [#5177] Added test for default source and target_branch values for merge requests

Posted by jo...@apache.org.
[#5177] Added test for default source and target_branch values for merge requests

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/dfafc625
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/dfafc625
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/dfafc625

Branch: refs/heads/cj/6464
Commit: dfafc62517a4ed101fa38f1e93c2b11e8d2146ef
Parents: 45732c0
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue Aug 13 19:18:32 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Tue Aug 13 21:28:33 2013 +0000

----------------------------------------------------------------------
 .../forgegit/tests/functional/test_controllers.py | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dfafc625/ForgeGit/forgegit/tests/functional/test_controllers.py
----------------------------------------------------------------------
diff --git a/ForgeGit/forgegit/tests/functional/test_controllers.py b/ForgeGit/forgegit/tests/functional/test_controllers.py
index d8c3f2c..597c5bb 100644
--- a/ForgeGit/forgegit/tests/functional/test_controllers.py
+++ b/ForgeGit/forgegit/tests/functional/test_controllers.py
@@ -33,6 +33,7 @@ from allura.lib import helpers as h
 from alluratest.controller import TestController
 from allura.tests.decorators import with_tool
 from forgegit.tests import with_git
+from forgegit import model as GM
 
 class _TestCase(TestController):
 
@@ -517,6 +518,23 @@ class TestFork(_TestCase):
                           params=dict(status='rejected')).follow()
         assert 'Merge Request #%s:  (rejected)' % mr_num in r, r
 
+    def test_merge_request_default_branches(self):
+        _select_val = lambda r, n: r.html.find('select', {'name': n}).find(selected=True).string
+        r = self.app.get('/p/test2/code/request_merge')
+        assert_equal(_select_val(r, 'source_branch'), 'master')
+        assert_equal(_select_val(r, 'target_branch'), 'master')
+        r = self.app.get('/p/test2/code/ci/zz/tree/').click('Request Merge')
+        assert_equal(_select_val(r, 'source_branch'), 'zz')
+        assert_equal(_select_val(r, 'target_branch'), 'master')
+        GM.Repository.query.get(_id=c.app.repo._id).default_branch_name = 'zz'
+        ThreadLocalORMSession.flush_all()
+        r = self.app.get('/p/test2/code/request_merge')
+        assert_equal(_select_val(r, 'source_branch'), 'master')
+        assert_equal(_select_val(r, 'target_branch'), 'zz')
+        r = self.app.get('/p/test2/code/ci/zz/tree/').click('Request Merge')
+        assert_equal(_select_val(r, 'source_branch'), 'zz')
+        assert_equal(_select_val(r, 'target_branch'), 'zz')
+
 class TestDiff(TestController):
 
     def setUp(self):


[23/25] git commit: [#6464] Use plain2markdown to escape ticket description and comments in GC Issues importer

Posted by jo...@apache.org.
[#6464] Use plain2markdown to escape ticket description and comments in GC Issues importer

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/8efe97c6
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/8efe97c6
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/8efe97c6

Branch: refs/heads/cj/6464
Commit: 8efe97c6e8ab6adb25dabbeb160564b1d3f47238
Parents: b94e844
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Thu Aug 8 20:44:54 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 14 15:41:13 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/tracker.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8efe97c6/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 297f65a..a997429 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -80,7 +80,7 @@ class GoogleCodeTrackerImporter(ToolImporter):
                 '{body}').format(
                     creator=issue.get_issue_creator(),
                     owner=issue.get_issue_owner(),
-                    body=issue.get_issue_description(),
+                    body=h.plain2markdown(issue.get_issue_description(), True),
                 )
         ticket.add_multiple_attachments(issue.get_issue_attachments())
 
@@ -108,7 +108,7 @@ class GoogleCodeTrackerImporter(ToolImporter):
                         '\n'
                         '{updates}').format(
                             author=comment.author,
-                            body=comment.body,
+                            body=h.plain2markdown(comment.body, True),
                             updates='\n'.join(
                                 '**%s** %s' % (k,v)
                                 for k,v in comment.updates.items()


[03/25] git commit: [#6458] Add google-code wiki page extraction

Posted by jo...@apache.org.
[#6458] Add google-code wiki page extraction

Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/3600da20
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/3600da20
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/3600da20

Branch: refs/heads/cj/6464
Commit: 3600da202a8895a2fbf206f4bed17506a65ff4ef
Parents: 6711c10
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Jul 30 14:39:04 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Aug 8 21:25:45 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/__init__.py   | 17 ++++++++++++++---
 .../forgeimporters/tests/google/test_extractor.py  |  9 +++++++++
 2 files changed, 23 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/3600da20/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 57e384b..8c91fd3 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -34,11 +34,13 @@ from allura import model as M
 log = logging.getLogger(__name__)
 
 class GoogleCodeProjectExtractor(object):
+    BASE_URL = 'http://code.google.com'
     RE_REPO_TYPE = re.compile(r'(svn|hg|git)')
 
     PAGE_MAP = {
-            'project_info': 'http://code.google.com/p/%s/',
-            'source_browse': 'http://code.google.com/p/%s/source/browse/',
+            'project_info': BASE_URL + '/p/%s/',
+            'source_browse': BASE_URL + '/p/%s/source/browse/',
+            'wiki_index': BASE_URL + '/p/%s/w/list',
         }
 
     LICENSE_MAP = defaultdict(lambda:'Other/Proprietary License', {
@@ -58,7 +60,8 @@ class GoogleCodeProjectExtractor(object):
 
     def __init__(self, allura_project, gc_project_name, page):
         self.project = allura_project
-        self.url = self.PAGE_MAP[page] % urllib.quote(gc_project_name)
+        self.gc_project_name = gc_project_name
+        self.url = self.PAGE_MAP[page] % urllib.quote(self.gc_project_name)
         self.page = BeautifulSoup(urllib2.urlopen(self.url))
 
     def get_short_description(self):
@@ -92,3 +95,11 @@ class GoogleCodeProjectExtractor(object):
             return re_match.group(0)
         else:
             raise Exception("Unknown repo type: {0}".format(repo_type.text))
+
+    def get_wiki_pages(self):
+        RE_WIKI_PAGE_URL = r'^/p/{0}/wiki/.*$'.format(self.gc_project_name)
+        seen = set()
+        for a in self.page.find(id="resultstable").findAll("a"):
+            if re.match(RE_WIKI_PAGE_URL, a['href']) and a['href'] not in seen:
+                yield (a.text, self.BASE_URL + a['href'])
+                seen.add(a['href'])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/3600da20/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index 1a3a87c..250759f 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -118,3 +118,12 @@ class TestGoogleCodeProjectExtractor(TestCase):
         with self.assertRaises(Exception) as cm:
             extractor.get_repo_type()
         self.assertEqual(str(cm.exception), "Unknown repo type: cvs")
+
+    def test_get_wiki_pages(self):
+        extractor = self._make_extractor('''
+        <div id="resultstable">
+            <a href="#">Link that's not a wiki page</a>
+            <a href="/p/my-project/wiki/PageOne">PageOne</a>
+        </div>''')
+        self.assertEqual(list(extractor.get_wiki_pages()), [
+            ('PageOne', 'http://code.google.com/p/my-project/wiki/PageOne')])


[16/25] git commit: [#6464] Changed tool_label on GC code importer

Posted by jo...@apache.org.
[#6464] Changed tool_label on GC code importer

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/f7da58e7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/f7da58e7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/f7da58e7

Branch: refs/heads/cj/6464
Commit: f7da58e73cfef6dbd7b60be13d8b5fcff3ae5173
Parents: c3ab845
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Wed Aug 7 01:14:26 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 14 15:41:12 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/code.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/f7da58e7/ForgeImporters/forgeimporters/google/code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/code.py b/ForgeImporters/forgeimporters/google/code.py
index 2f8d19f..11dd7a6 100644
--- a/ForgeImporters/forgeimporters/google/code.py
+++ b/ForgeImporters/forgeimporters/google/code.py
@@ -105,7 +105,7 @@ class GoogleRepoImporter(ToolImporter):
         """ Import a Google Code repo into a new SVN, Git, or Hg Allura tool.
 
         """
-        extractor = GoogleCodeProjectExtractor(project, project_name, 'source_browse')
+        extractor = GoogleCodeProjectExtractor(project_name, 'source_browse')
         repo_type = extractor.get_repo_type()
         repo_url = get_repo_url(project_name, repo_type)
         app = project.install_app(


[20/25] git commit: [#6464] Fixed issue with custom fields not persisting and added tests

Posted by jo...@apache.org.
[#6464] Fixed issue with custom fields not persisting and added tests

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/4e0956b3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/4e0956b3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/4e0956b3

Branch: refs/heads/cj/6464
Commit: 4e0956b34b07952511dd33cafa20899b8c325fcb
Parents: e4f4e09
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Mon Aug 12 22:25:23 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 14 15:41:13 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/helpers.py                    |   4 +-
 .../forgeimporters/google/__init__.py           |  19 ++
 ForgeImporters/forgeimporters/google/tracker.py |  65 ++---
 .../tests/google/functional/__init__.py         |  17 ++
 .../tests/google/functional/test_tracker.py     | 260 +++++++++++++++++++
 .../forgeimporters/tests/google/test_tracker.py |  25 +-
 6 files changed, 341 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4e0956b3/Allura/allura/lib/helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 9f6fbfc..3346901 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -96,7 +96,7 @@ re_preserve_spaces = re.compile(r'''
     ''', re.VERBOSE)
 re_angle_bracket_open = re.compile('<')
 re_angle_bracket_close = re.compile('>')
-md_chars_matcher_all = re.compile(r"([`\*_{}\[\]\(\)#!\\.+-])")
+md_chars_matcher_all = re.compile(r"([`\*_{}\[\]\(\)#!\\\.+-])")
 
 def make_safe_path_portion(ustr, relaxed=True):
     """Return an ascii representation of ``ustr`` that conforms to mount point
@@ -896,7 +896,7 @@ def plain2markdown(text, preserve_multiple_spaces=False, has_html_entities=False
         text = html2text.escape_md_section(text, snob=True)
     except ImportError:
         # fall back to just escaping any MD-special chars
-        text = md_chars_matcher.sub(r"\\\\1", text)
+        text = md_chars_matcher_all.sub(r"\\\1", text)
     # prevent < and > from becoming tags
     text = re_angle_bracket_open.sub('&lt;', text)
     text = re_angle_bracket_close.sub('&gt;', text)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4e0956b3/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 37f70db..67ef14d 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -29,6 +29,7 @@ import logging
 
 from BeautifulSoup import BeautifulSoup
 
+from allura.lib import helpers as h
 from allura import model as M
 
 
@@ -261,6 +262,24 @@ class Comment(object):
         else:
             self.attachments = []
 
+    @property
+    def annotated_text(self):
+        text = (
+                u'*Originally posted by:* [{author.name}]({author.link})\n'
+                u'\n'
+                u'{body}\n'
+                u'\n'
+                u'{updates}'
+            ).format(
+                author=self.author,
+                body=h.plain2markdown(self.body, True),
+                updates='\n'.join(
+                        '**%s** %s' % (k,v)
+                        for k,v in self.updates.items()
+                    ),
+            )
+        return text
+
 class Attachment(object):
     def __init__(self, tag):
         self.filename = _as_text(tag).strip().split()[0]

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4e0956b3/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index bd3a57f..fefd695 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -19,6 +19,7 @@ from collections import defaultdict
 from datetime import datetime
 
 from pylons import tmpl_context as c
+from pylons import app_globals as g
 from ming.orm import session, ThreadLocalORMSession
 
 from allura import model as M
@@ -44,22 +45,27 @@ class GoogleCodeTrackerImporter(ToolImporter):
 
     def import_tool(self, project, user, project_name, mount_point=None,
             mount_label=None, **kw):
-        c.app = project.install_app('tickets', mount_point, mount_label)
+        app = project.install_app('tickets', mount_point, mount_label)
+        app.globals.open_status_names = 'New Accepted Started'
+        app.globals.closed_status_names = 'Fixed Verified Invalid Duplicate WontFix Done'
         ThreadLocalORMSession.flush_all()
-        c.app.globals.open_status_names = 'New Accepted Started'
-        c.app.globals.closed_status_names = 'Fixed Verified Invalid Duplicate WontFix Done'
         self.custom_fields = {}
         try:
             M.session.artifact_orm_session._get().skip_mod_date = True
-            for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
-                ticket = TM.Ticket.new()
-                self.process_fields(ticket, issue)
-                self.process_labels(ticket, issue)
-                self.process_comments(ticket, issue)
-                session(ticket).flush(ticket)
-                session(ticket).expunge(ticket)
-            self.postprocess_custom_fields()
-            ThreadLocalORMSession.flush_all()
+            with h.push_config(c, user=M.User.anonymous(), app=app):
+                for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
+                    ticket = TM.Ticket.new()
+                    self.process_fields(ticket, issue)
+                    self.process_labels(ticket, issue)
+                    self.process_comments(ticket, issue)
+                    session(ticket).flush(ticket)
+                    session(ticket).expunge(ticket)
+                # app.globals gets expunged every time Ticket.new() is called :-(
+                app.globals = TM.Globals.query.get(app_config_id=app.config._id)
+                app.globals.custom_fields = self.postprocess_custom_fields()
+                ThreadLocalORMSession.flush_all()
+            g.post_event('project_updated')
+            return app
         finally:
             M.session.artifact_orm_session._get().skip_mod_date = False
 
@@ -78,13 +84,18 @@ class GoogleCodeTrackerImporter(ToolImporter):
         ticket.status = issue.get_issue_status()
         ticket.created_date = datetime.strptime(issue.get_issue_created_date(), '%c')
         ticket.mod_date = datetime.strptime(issue.get_issue_mod_date(), '%c')
+        owner = issue.get_issue_owner()
+        if owner:
+            owner_line = '*Originally owned by:* [{owner.name}]({owner.link})\n'.format(owner=owner)
+        else:
+            owner_line = ''
         ticket.description = (
                 u'*Originally created by:* [{creator.name}]({creator.link})\n'
-                '*Originally owned by:* [{owner.name}]({owner.link})\n'
-                '\n'
-                '{body}').format(
+                u'{owner}'
+                u'\n'
+                u'{body}').format(
                     creator=issue.get_issue_creator(),
-                    owner=issue.get_issue_owner(),
+                    owner=owner_line,
                     body=h.plain2markdown(issue.get_issue_description(), True),
                 )
         ticket.add_multiple_attachments(issue.get_issue_attachments())
@@ -106,25 +117,14 @@ class GoogleCodeTrackerImporter(ToolImporter):
     def process_comments(self, ticket, issue):
         for comment in issue.iter_comments():
             p = ticket.discussion_thread.add_post(
-                    text = (
-                        u'*Originally posted by:* [{author.name}]({author.link})\n'
-                        '\n'
-                        '{body}\n'
-                        '\n'
-                        '{updates}').format(
-                            author=comment.author,
-                            body=h.plain2markdown(comment.body, True),
-                            updates='\n'.join(
-                                '**%s** %s' % (k,v)
-                                for k,v in comment.updates.items()
-                            ),
-                    )
+                    text = comment.annotated_text,
+                    ignore_security = True,
+                    timestamp = datetime.strptime(comment.created_date, '%c'),
                 )
-            p.created_date = p.timestamp = datetime.strptime(comment.created_date, '%c')
             p.add_multiple_attachments(comment.attachments)
 
     def postprocess_custom_fields(self):
-        c.app.globals.custom_fields = []
+        custom_fields = []
         for name, field in self.custom_fields.iteritems():
             if field['name'] == '_milestone':
                 field['milestones'] = [{
@@ -137,4 +137,5 @@ class GoogleCodeTrackerImporter(ToolImporter):
                 field['options'] = ' '.join(field['options'])
             else:
                 field['options'] = ''
-            c.app.globals.custom_fields.append(field)
+            custom_fields.append(field)
+        return custom_fields

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4e0956b3/ForgeImporters/forgeimporters/tests/google/functional/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/__init__.py b/ForgeImporters/forgeimporters/tests/google/functional/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/google/functional/__init__.py
@@ -0,0 +1,17 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4e0956b3/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
new file mode 100644
index 0000000..206ac7c
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
@@ -0,0 +1,260 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+from unittest import TestCase
+import pkg_resources
+from functools import wraps
+from datetime import datetime
+
+from BeautifulSoup import BeautifulSoup
+import mock
+from ming.orm import ThreadLocalORMSession
+from pylons import tmpl_context as c
+from IPython.testing.decorators import module_not_available, skipif
+
+from alluratest.controller import setup_basic_test
+from allura import model as M
+from forgetracker import model as TM
+from .... import google
+from ....google import tracker
+
+
+def without_html2text(func):
+    @wraps(func)
+    def wrapped(*args, **kw):
+        try:
+            import html2text
+        except ImportError:
+            return func(*args, **kw)
+        else:
+            with mock.patch.object(html2text, 'escape_md_section') as ems:
+                ems.side_effect = ImportError
+                return func(*args, **kw)
+    return wrapped
+
+class TestGCTrackerImporter(TestCase):
+    def _make_extractor(self, html):
+        with mock.patch.object(google, 'urllib2') as urllib2:
+            urllib2.urlopen.return_value = ''
+            extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
+        extractor.page = BeautifulSoup(html)
+        extractor.url = "http://test/issue/?id=1"
+        return extractor
+
+    def _make_ticket(self, issue):
+        self.assertIsNone(self.project.app_instance('test-issue'))
+        with mock.patch.object(google, 'urllib2') as urllib2,\
+             mock.patch.object(google.tracker, 'GoogleCodeProjectExtractor') as GPE:
+            urllib2.urlopen = lambda url: mock.Mock(read=lambda: url)
+            GPE.iter_issues.return_value = [issue]
+            gti = google.tracker.GoogleCodeTrackerImporter()
+            gti.import_tool(self.project, self.user, 'test-issue-project', mount_point='test-issue')
+        c.app = self.project.app_instance('test-issue')
+        query = TM.Ticket.query.find({'app_config_id': c.app.config._id})
+        self.assertEqual(query.count(), 1)
+        ticket = query.all()[0]
+        return ticket
+
+    def setUp(self, *a, **kw):
+        super(TestGCTrackerImporter, self).setUp(*a, **kw)
+        setup_basic_test()
+        self.empty_issue = self._make_extractor(open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/empty-issue.html')).read())
+        self.test_issue = self._make_extractor(open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read())
+        c.project = self.project = M.Project.query.get(shortname='test')
+        c.user = self.user = M.User.query.get(username='test-admin')
+
+    def test_empty_issue(self):
+        ticket = self._make_ticket(self.empty_issue)
+        self.assertEqual(ticket.summary, 'Empty Issue')
+        self.assertEqual(ticket.description, '*Originally created by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n\nEmpty')
+        self.assertEqual(ticket.status, '')
+        self.assertEqual(ticket.milestone, '')
+        self.assertEqual(ticket.custom_fields, {})
+
+    @without_html2text
+    def test_issue_basic_fields(self):
+        anon = M.User.anonymous()
+        ticket = self._make_ticket(self.test_issue)
+        self.assertEqual(ticket.reported_by, anon)
+        self.assertIsNone(ticket.assigned_to_id)
+        self.assertEqual(ticket.summary, 'Test Issue')
+        self.assertEqual(ticket.description,
+                '*Originally created by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '*Originally owned by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '\n'
+                'Test \\*Issue\\* for testing\n'
+                '\n'
+                '&nbsp; 1\\. Test List\n'
+                '&nbsp; 2\\. Item\n'
+                '\n'
+                '\\*\\*Testing\\*\\*\n'
+                '\n'
+                ' \\* Test list 2\n'
+                ' \\* Item\n'
+                '\n'
+                '\\# Test Section\n'
+                '\n'
+                '&nbsp;&nbsp;&nbsp; p = source\\.test\\_issue\\.post\\(\\)\n'
+                '&nbsp;&nbsp;&nbsp; p\\.count = p\\.count \\*5 \\#\\* 6\n'
+                '\n'
+                'That\'s all'
+            )
+        self.assertEqual(ticket.status, 'Started')
+        self.assertEqual(ticket.created_date, datetime(2013, 8, 8, 15, 33, 52))
+        self.assertEqual(ticket.mod_date, datetime(2013, 8, 8, 15, 36, 57))
+        self.assertEqual(ticket.custom_fields, {
+                '_priority': 'Medium',
+                '_opsys': 'All, OSX, Windows',
+                '_component': 'Logic',
+                '_type': 'Defect',
+                '_milestone': 'Release1.0'
+            })
+        self.assertEqual(ticket.labels, ['Performance', 'Security'])
+
+    @skipif(module_not_available('html2text'))
+    def test_html2text_escaping(self):
+        ticket = self._make_ticket(self.test_issue)
+        self.assertEqual(ticket.description,
+                '*Originally created by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '*Originally owned by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '\n'
+                'Test \\*Issue\\* for testing\n'
+                '\n'
+                '&nbsp; 1. Test List\n'
+                '&nbsp; 2. Item\n'
+                '\n'
+                '\\*\\*Testing\\*\\*\n'
+                '\n'
+                ' \\* Test list 2\n'
+                ' \\* Item\n'
+                '\n'
+                '\\# Test Section\n'
+                '\n'
+                '&nbsp;&nbsp;&nbsp; p = source.test\\_issue.post\\(\\)\n'
+                '&nbsp;&nbsp;&nbsp; p.count = p.count \\*5 \\#\\* 6\n'
+                '\n'
+                'That\'s all'
+            )
+
+    def _assert_attachments(self, actual, *expected):
+        self.assertEqual(actual.count(), len(expected))
+        atts = set((a.filename, a.content_type, a.rfile().read()) for a in actual)
+        self.assertEqual(atts, set(expected))
+
+    def test_attachements(self):
+        ticket = self._make_ticket(self.test_issue)
+        self._assert_attachments(ticket.attachments,
+                ('at1.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000000&name=at1.txt&token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255'),
+                ('at2.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000001&name=at2.txt&token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255'),
+            )
+
+    @without_html2text
+    def test_comments(self):
+        anon = M.User.anonymous()
+        ticket = self._make_ticket(self.test_issue)
+        actual_comments = ticket.discussion_thread.find_posts()
+        expected_comments = [
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 35, 15),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Test \\*comment\\* is a comment\n'
+                            '\n'
+                            '**Labels:** -OpSys-Linux OpSys-Windows\n'
+                            '**Status:** Started'
+                        ),
+                    'attachments': [
+                            ('at2.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=60001000&name=at2.txt&token=JOSo4duwaN2FCKZrwYOQ-nx9r7U%3A1376001446667'),
+                        ],
+                },
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 35, 34),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Another comment\n\n'
+                        ),
+                },
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 36, 39),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Last comment\n\n'
+                        ),
+                    'attachments': [
+                            ('at4.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=60003000&name=at4.txt&token=6Ny2zYHmV6b82dqxyoiH6HUYoC4%3A1376001446667'),
+                            ('at1.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=60003001&name=at1.txt&token=NS8aMvWsKzTAPuY2kniJG5aLzPg%3A1376001446667'),
+                        ],
+                },
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 36, 57),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Oh, I forgot one\n'
+                            '\n'
+                            '**Labels:** OpSys-OSX'
+                        ),
+                },
+            ]
+        self.assertEqual(len(actual_comments), len(expected_comments))
+        for actual, expected in zip(actual_comments, expected_comments):
+            self.assertEqual(actual.author(), anon)
+            self.assertEqual(actual.timestamp, expected['timestamp'])
+            self.assertEqual(actual.text, expected['text'])
+            if 'attachments' in expected:
+                self._assert_attachments(actual.attachments, *expected['attachments'])
+
+    def test_globals(self):
+        globals = self._make_ticket(self.test_issue).globals
+        self.assertItemsEqual(globals.custom_fields, [
+                {
+                    'label': 'Milestone',
+                    'name': '_milestone',
+                    'type': 'milestone',
+                    'options': '',
+                    'milestones': [
+                            {'name': 'Release1.0', 'due_date': None, 'complete': False},
+                        ],
+                },
+                {
+                    'label': 'Type',
+                    'name': '_type',
+                    'type': 'select',
+                    'options': 'Defect',
+                },
+                {
+                    'label': 'Priority',
+                    'name': '_priority',
+                    'type': 'select',
+                    'options': 'Medium',
+                },
+                {
+                    'label': 'OpSys',
+                    'name': '_opsys',
+                    'type': 'string',
+                    'options': '',
+                },
+                {
+                    'label': 'Component',
+                    'name': '_component',
+                    'type': 'string',
+                    'options': '',
+                },
+            ])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4e0956b3/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index a1f0a28..3efd97d 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -27,9 +27,10 @@ class TestTrackerImporter(TestCase):
     @mock.patch.object(tracker, 'c')
     @mock.patch.object(tracker, 'ThreadLocalORMSession')
     @mock.patch.object(tracker, 'session')
+    @mock.patch.object(tracker, 'M')
     @mock.patch.object(tracker, 'TM')
     @mock.patch.object(tracker, 'GoogleCodeProjectExtractor')
-    def test_import_tool(self, gpe, TM, session, tlos, c):
+    def test_import_tool(self, gpe, TM, M, session, tlos, c):
         importer = tracker.GoogleCodeTrackerImporter()
         importer.process_fields = mock.Mock()
         importer.process_labels = mock.Mock()
@@ -158,12 +159,14 @@ class TestTrackerImporter(TestCase):
                 mock.Mock(
                     author=_author(1),
                     body='text1',
+                    annotated_text='annotated1',
                     attachments='attachments1',
                     created_date='Mon Jul 15 00:00:00 2013',
                 ),
                 mock.Mock(
                     author=_author(2),
                     body='text2',
+                    annotated_text='annotated2',
                     attachments='attachments2',
                     created_date='Mon Jul 16 00:00:00 2013',
                 ),
@@ -177,24 +180,16 @@ class TestTrackerImporter(TestCase):
         importer = tracker.GoogleCodeTrackerImporter()
         importer.process_comments(ticket, issue)
         self.assertEqual(ticket.discussion_thread.add_post.call_args_list[0], mock.call(
-                text='*Originally posted by:* [author1](author1_link)\n'
-                '\n'
-                'text1\n'
-                '\n'
-                '**Foo:** Bar\n'
-                '**Baz:** Qux'
+                text='annotated1',
+                timestamp=datetime(2013, 7, 15),
+                ignore_security=True,
             ))
-        self.assertEqual(posts[0].created_date, datetime(2013, 7, 15))
-        self.assertEqual(posts[0].timestamp, datetime(2013, 7, 15))
         posts[0].add_multiple_attachments.assert_called_once_with('attachments1')
         self.assertEqual(ticket.discussion_thread.add_post.call_args_list[1], mock.call(
-                text='*Originally posted by:* [author2](author2_link)\n'
-                '\n'
-                'text2\n'
-                '\n'
+                text='annotated2',
+                timestamp=datetime(2013, 7, 16),
+                ignore_security=True,
             ))
-        self.assertEqual(posts[1].created_date, datetime(2013, 7, 16))
-        self.assertEqual(posts[1].timestamp, datetime(2013, 7, 16))
         posts[1].add_multiple_attachments.assert_called_once_with('attachments2')
 
     @mock.patch.object(tracker, 'c')


[04/25] git commit: [#6554] Fixed incorrect uses of h.re_project_name causing 404 in REST

Posted by jo...@apache.org.
[#6554] Fixed incorrect uses of h.re_project_name causing 404 in REST

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/9e3e3941
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/9e3e3941
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/9e3e3941

Branch: refs/heads/cj/6464
Commit: 9e3e39413fc2879b6a52ffdd5b6b45e142cf1f72
Parents: 3cd0160
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Mon Aug 12 19:47:18 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Mon Aug 12 19:47:18 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/rest.py           |  7 ++++++-
 Allura/allura/model/project.py              |  6 ++++--
 Allura/allura/tests/functional/test_rest.py |  9 +++++++++
 Allura/allura/tests/model/test_project.py   | 10 ++++++----
 4 files changed, 25 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9e3e3941/Allura/allura/controllers/rest.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/rest.py b/Allura/allura/controllers/rest.py
index c976c21..6a41be3 100644
--- a/Allura/allura/controllers/rest.py
+++ b/Allura/allura/controllers/rest.py
@@ -32,6 +32,8 @@ from ming.utils import LazyProperty
 from allura import model as M
 from allura.lib import helpers as h
 from allura.lib import security
+from allura.lib import plugin
+from allura.lib.exceptions import Invalid
 
 log = logging.getLogger(__name__)
 action_logger = h.log_action(log, 'API:')
@@ -243,7 +245,10 @@ class NeighborhoodRestController(object):
 
     @expose()
     def _lookup(self, name, *remainder):
-        if not h.re_project_name.match(name):
+        provider = plugin.ProjectRegistrationProvider.get()
+        try:
+            provider.shortname_validator.to_python(name, check_allowed=False, neighborhood=self._neighborhood)
+        except Invalid as e:
             raise exc.HTTPNotFound, name
         name = self._neighborhood.shortname_prefix + name
         project = M.Project.query.get(shortname=name, neighborhood_id=self._neighborhood._id, deleted=False)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9e3e3941/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 435cbbb..db7da13 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -638,9 +638,11 @@ class Project(MappedClass, ActivityNode, ActivityObject):
                 return ac
 
     def new_subproject(self, name, install_apps=True, user=None, project_name=None):
-        if not h.re_project_name.match(name):
-            raise exceptions.ToolError, 'Mount point "%s" is invalid' % name
         provider = plugin.ProjectRegistrationProvider.get()
+        try:
+            provider.shortname_validator.to_python(name, check_allowed=False, neighborhood=self.neighborhood)
+        except exceptions.Invalid as e:
+            raise exceptions.ToolError, 'Mount point "%s" is invalid' % name
         return provider.register_subproject(self, name, user or c.user, install_apps, project_name=project_name)
 
     def ordered_mounts(self, include_hidden=False):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9e3e3941/Allura/allura/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index fcfb373..d5ed769 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -27,6 +27,7 @@ from nose.tools import assert_equal, assert_in, assert_not_in
 from allura.tests import decorators as td
 from alluratest.controller import TestRestApiBase
 from allura.lib import helpers as h
+from allura.lib.exceptions import Invalid
 from allura import model as M
 
 class TestRestHome(TestRestApiBase):
@@ -141,3 +142,11 @@ class TestRestHome(TestRestApiBase):
                         'qux_24hr': 0,
                     },
                 })
+
+    def test_name_validation(self):
+        r = self.api_get('/rest/p/test/')
+        assert r.status_int == 200
+        with mock.patch('allura.lib.plugin.ProjectRegistrationProvider') as Provider:
+            Provider.get().shortname_validator.to_python.side_effect = Invalid('name', 'value', {})
+            r = self.api_get('/rest/p/test/')
+            assert r.status_int == 404

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9e3e3941/Allura/allura/tests/model/test_project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/model/test_project.py b/Allura/allura/tests/model/test_project.py
index 33791f4..2cd7892 100644
--- a/Allura/allura/tests/model/test_project.py
+++ b/Allura/allura/tests/model/test_project.py
@@ -28,8 +28,8 @@ from allura import model as M
 from allura.lib import helpers as h
 from allura.tests import decorators as td
 from alluratest.controller import setup_basic_test, setup_global_objects
-from allura.lib.exceptions import ToolError
-from mock import MagicMock
+from allura.lib.exceptions import ToolError, Invalid
+from mock import MagicMock, patch
 
 
 def setUp():
@@ -93,8 +93,10 @@ def test_project():
 def test_subproject():
     project = M.Project.query.get(shortname='test')
     with td.raises(ToolError):
-        # name exceeds 15 chars
-        sp = project.new_subproject('test-project-nose')
+        with patch('allura.lib.plugin.ProjectRegistrationProvider') as Provider:
+            Provider.get().shortname_validator.to_python.side_effect = Invalid('name', 'value', {})
+            # name doesn't validate
+            sp = project.new_subproject('test-proj-nose')
     sp = project.new_subproject('test-proj-nose')
     spp = sp.new_subproject('spp')
     ThreadLocalORMSession.flush_all()


[11/25] git commit: [#4818] don't error if tool doesn't exist any more

Posted by jo...@apache.org.
[#4818] don't error if tool doesn't exist any more


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/c0dbb7fe
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/c0dbb7fe
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/c0dbb7fe

Branch: refs/heads/cj/6464
Commit: c0dbb7fe53a0fdc244cb6c9904533564dd7e6559
Parents: df942cb
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Fri Aug 9 20:01:21 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Aug 14 13:12:13 2013 +0000

----------------------------------------------------------------------
 Allura/allura/ext/admin/admin_main.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c0dbb7fe/Allura/allura/ext/admin/admin_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index 92efacb..98a34c8 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -153,7 +153,7 @@ class AdminApp(Application):
 
 
 class AdminExtensionLookup(object):
-    
+
     @expose()
     def _lookup(self, name, *remainder):
         for ext_name, admin_extension in g.entry_points['admin'].iteritems():
@@ -177,8 +177,11 @@ class ProjectAdminController(BaseController):
     @with_trailing_slash
     @expose('jinja:allura.ext.admin:templates/project_admin.html')
     def index(self, **kw):
-        scm_tools = [tool for tool in c.project.app_configs if issubclass(
-                g.entry_points["tool"][tool.tool_name], RepositoryApp)]
+        scm_tools = []
+        for tool in c.project.app_configs:
+            app = g.entry_points["tool"].get(tool.tool_name)
+            if app and issubclass(app, RepositoryApp):
+                scm_tools.append(tool)
         return dict(scm_tools=scm_tools)
 
     @without_trailing_slash