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/12 18:40:46 UTC

[01/50] git commit: [#6139] ticket:399 Skip pages that can't be parsed (non-wiki)

Updated Branches:
  refs/heads/cj/6422 70d4cdb35 -> 387f1cf9b


[#6139] ticket:399 Skip pages that can't be parsed (non-wiki)


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

Branch: refs/heads/cj/6422
Commit: be9d8225fc07c1021f631b320df78270f8392787
Parents: 24d39a7
Author: Igor Bondarenko <je...@gmail.com>
Authored: Fri Jul 26 08:22:51 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Tue Jul 30 19:29:23 2013 +0000

----------------------------------------------------------------------
 ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/be9d8225/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py b/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
index ef931b3..0038dd9 100644
--- a/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
+++ b/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
@@ -18,6 +18,7 @@
 import re
 import sys
 import json
+import traceback
 from urllib import quote, unquote
 from urlparse import urljoin, urlsplit
 
@@ -101,7 +102,14 @@ class WikiExporter(object):
         self.options = options
 
     def export(self, out):
-        pages = [self.get_page(title) for title in self.page_list()]
+        pages = []
+        for title in self.page_list():
+            try:
+                pages.append(self.get_page(title))
+            except:
+                self.log('Cannot fetch page %s. Skipping' % title)
+                self.log(traceback.format_exc())
+                continue
         out.write(json.dumps(pages, indent=2, sort_keys=True))
         out.write('\n')
 


[49/50] 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/6422
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')])


[37/50] git commit: [#6480] Importer bug fixes

Posted by jo...@apache.org.
[#6480] Importer bug fixes

- Normalize trac urls
- Flush new app configs and related objects before importing artifacts
- Add options needed for WikiExporter

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/75539774
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/75539774
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/75539774

Branch: refs/heads/cj/6422
Commit: 755397743b8956fe2342eede82d4163b6fd92a3d
Parents: 35cc655
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 18:57:37 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/base.py                    |  4 +++-
 ForgeImporters/forgeimporters/google/code.py             |  1 +
 ForgeImporters/forgeimporters/trac/tickets.py            |  9 +++++++--
 ForgeImporters/forgeimporters/trac/wiki.py               | 10 +++++++++-
 ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py |  4 ++++
 5 files changed, 24 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/75539774/ForgeImporters/forgeimporters/base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index 5652474..7ad720c 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -52,7 +52,9 @@ class ProjectImportForm(schema.Schema):
 @task
 def import_tool(importer_name, project_name, mount_point=None, mount_label=None, **kw):
     importer = ToolImporter.by_name(importer_name)
-    importer.import_tool(c.project, mount_point, mount_label, **kw)
+    importer.import_tool(project=c.project, user=c.user,
+            mount_point=mount_point,
+            mount_label=mount_label, **kw)
 
 
 class ProjectImporter(BaseController):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/75539774/ForgeImporters/forgeimporters/google/code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/code.py b/ForgeImporters/forgeimporters/google/code.py
index ef7f800..4457b55 100644
--- a/ForgeImporters/forgeimporters/google/code.py
+++ b/ForgeImporters/forgeimporters/google/code.py
@@ -101,6 +101,7 @@ class GoogleRepoImporter(ToolImporter):
     tool_description = 'Import your SVN, Git, or Hg repo from Google Code'
 
     def import_tool(self, project, project_name, mount_point=None, mount_label=None):
+    def import_tool(self, project, project_name, mount_point=None, mount_label=None, **kw):
         """ Import a Google Code repo into a new SVN, Git, or Hg Allura tool.
 
         """

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/75539774/ForgeImporters/forgeimporters/trac/tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index 969dfd2..0f13649 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -24,6 +24,7 @@ import json
 import formencode as fe
 from formencode import validators as fev
 
+from ming.orm import session
 from pylons import tmpl_context as c
 from pylons import app_globals as g
 from tg import (
@@ -84,21 +85,25 @@ class TracTicketImporter(ToolImporter):
     tool_description = 'Import your tickets from Trac'
 
     def import_tool(self, project=None, mount_point=None, mount_label=None,
-            trac_url=None, user=None):
+            trac_url=None, user=None, **kw):
         """ Import Trac tickets into a new Allura Tracker tool.
 
         """
+        trac_url = trac_url.rstrip('/') + '/'
         mount_point = mount_point or 'tickets'
         app = project.install_app(
                 'Tickets',
                 mount_point=mount_point,
                 mount_label=mount_label or 'Tickets',
                 )
-        export = TracExport(trac_url)
+        session(app.config).flush(app.config)
+        session(app.globals).flush(app.globals)
+        export = [ticket for ticket in TracExport(trac_url)]
         export_string = json.dumps(export, cls=DateJSONEncoder)
         api_ticket = ApiTicket(user_id=user._id,
                 capabilities={"import": ["Projects", project.shortname]},
                 expires=datetime.utcnow() + timedelta(minutes=60))
+        session(api_ticket).flush(api_ticket)
         cli = AlluraImportApiClient(config['base_url'], api_ticket.api_key,
                 api_ticket.secret_key, False)
         import_tracker(cli, project.shortname, mount_point, {},

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/75539774/ForgeImporters/forgeimporters/trac/wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/wiki.py b/ForgeImporters/forgeimporters/trac/wiki.py
index 2417863..e7ead86 100644
--- a/ForgeImporters/forgeimporters/trac/wiki.py
+++ b/ForgeImporters/forgeimporters/trac/wiki.py
@@ -25,6 +25,7 @@ import tempfile
 import formencode as fe
 from formencode import validators as fev
 
+from ming.orm import session
 from pylons import tmpl_context as c
 from pylons import app_globals as g
 from tg import (
@@ -83,25 +84,32 @@ class TracWikiImporter(ToolImporter):
     tool_description = 'Import your wiki from Trac'
 
     def import_tool(self, project=None, mount_point=None, mount_label=None,
-            trac_url=None, user=None):
+            trac_url=None, user=None, **kw):
         """ Import Trac wiki into a new Allura Wiki tool.
 
         """
+        trac_url = trac_url.rstrip('/') + '/'
         mount_point = mount_point or 'wiki'
         app = project.install_app(
                 'Wiki',
                 mount_point=mount_point,
                 mount_label=mount_label or 'Wiki',
                 )
+        session(app.config).flush(app.config)
         api_ticket = ApiTicket(user_id=user._id,
                 capabilities={"import": ["Projects", project.shortname]},
                 expires=datetime.utcnow() + timedelta(minutes=60))
+        session(api_ticket).flush(api_ticket)
         options = argparse.Namespace()
         options.api_key = api_ticket.api_key
         options.secret_key = api_ticket.secret_key
         options.project = project.shortname
         options.wiki = mount_point
         options.base_url = config['base_url']
+        options.verbose = False
+        options.converter = 'html2text'
+        options.import_opts = []
+        options.user_map_file = None
         with tempfile.NamedTemporaryFile() as f:
             WikiExporter(trac_url, options).export(f)
             f.flush()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/75539774/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py b/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
index 0038dd9..7f111d7 100644
--- a/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
+++ b/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+import logging
 import re
 import sys
 import json
@@ -36,6 +37,8 @@ except:
 
 from BeautifulSoup import BeautifulSoup
 
+log = logging.getLogger(__name__)
+
 
 class WikiExporter(object):
 
@@ -114,6 +117,7 @@ class WikiExporter(object):
         out.write('\n')
 
     def log(self, msg):
+        log.info(msg)
         if self.options.verbose:
             print >>sys.stderr, msg
 


[23/50] git commit: [#3154] ticket:407 ForgeBlog API refactoring

Posted by jo...@apache.org.
[#3154]  ticket:407 ForgeBlog API refactoring


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

Branch: refs/heads/cj/6422
Commit: e23020d03926ed022434aade5762221d9f3e486d
Parents: af5c8e6
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Fri Jul 26 13:34:29 2013 +0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Jul 31 21:00:24 2013 +0000

----------------------------------------------------------------------
 AlluraTest/alluratest/controller.py             |  2 +-
 ForgeBlog/forgeblog/main.py                     | 42 ++++++--------------
 ForgeBlog/forgeblog/model/blog.py               | 15 +++++++
 .../forgeblog/tests/functional/test_rest.py     | 36 +++++++++++++----
 .../forgeblog/tests/unit/test_blog_post.py      |  9 +++++
 5 files changed, 67 insertions(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e23020d0/AlluraTest/alluratest/controller.py
----------------------------------------------------------------------
diff --git a/AlluraTest/alluratest/controller.py b/AlluraTest/alluratest/controller.py
index 95d9b4b..7b38700 100644
--- a/AlluraTest/alluratest/controller.py
+++ b/AlluraTest/alluratest/controller.py
@@ -168,7 +168,7 @@ class TestRestApiBase(TestController):
         response = fn(
             str(path),
             params=params,
-            status=[200, 302, 400, 403, 404])
+            status=[200, 201, 302, 400, 403, 404])
         if response.status_int == 302:
             return response.follow()
         else:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e23020d0/ForgeBlog/forgeblog/main.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/main.py b/ForgeBlog/forgeblog/main.py
index 0f4e7e6..8bc56c0 100644
--- a/ForgeBlog/forgeblog/main.py
+++ b/ForgeBlog/forgeblog/main.py
@@ -249,15 +249,7 @@ class RootController(BaseController, FeedController):
     @without_trailing_slash
     def save(self, **kw):
         require_access(c.app, 'write')
-        post = BM.BlogPost()
-        for k,v in kw.iteritems():
-            setattr(post, k, v)
-        post.neighborhood_id=c.project.neighborhood_id
-        post.make_slug()
-        post.commit()
-        M.Thread.new(discussion_id=post.app_config.discussion_id,
-               ref_id=post.index_id(),
-               subject='%s discussion' % post.title)
+        post = BM.BlogPost.new(**kw)
         redirect(h.really_unicode(post.url()).encode('utf-8'))
 
 
@@ -451,33 +443,25 @@ class RootRestController(BaseController):
         require_access(c.app, 'read')
 
     @expose('json:')
-    def index(self, title='', text='', state='draft', labels='', **kw):
+    def index(self, title='', text='', state='draft', labels='', limit=10, page=0, **kw):
         if request.method == 'POST':
             require_access(c.app, 'write')
-            post = BM.BlogPost()
-            post.title = title
-            post.state = state
-            post.text = text
-            post.labels = labels.split(',')
-            post.neighborhood_id = c.project.neighborhood_id
-            post.make_slug()
-            M.Thread.new(discussion_id=post.app_config.discussion_id,
-                         ref_id=post.index_id(),
-                         subject='%s discussion' % post.title)
-
-            post.viewable_by = ['all']
-            post.commit()
-            return post.__json__()
+            post = BM.BlogPost.new(
+                title=title,
+                state=state,
+                text=text,
+                labels=labels.split(','),
+                **kw)
+            return exc.HTTPCreated(headers=dict(Location=h.absurl('/rest' + post.url())))
+
         else:
+            result = RootController().index(limit=limit, page=page)
+            posts = result['posts']
             post_titles = []
-            query_filter = dict(app_config_id=c.app.config._id, deleted=False)
-            if not has_access(c.app, 'write')():
-                query_filter['state'] = 'published'
-            posts = BM.BlogPost.query.find(query_filter)
             for post in posts:
                 if has_access(post, 'read')():
                     post_titles.append({'title': post.title, 'url': h.absurl('/rest' + post.url())})
-            return dict(posts=post_titles)
+            return dict(posts=post_titles, count=result['count'], limit=result['limit'], page=result['page'])
 
     @expose()
     def _lookup(self, year=None, month=None, title=None, *rest):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e23020d0/ForgeBlog/forgeblog/model/blog.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/model/blog.py b/ForgeBlog/forgeblog/model/blog.py
index 07983a0..2cd16e0 100644
--- a/ForgeBlog/forgeblog/model/blog.py
+++ b/ForgeBlog/forgeblog/model/blog.py
@@ -256,8 +256,23 @@ class BlogPost(M.VersionedArtifact, ActivityObject):
             M.Notification.post(
                 artifact=self, topic='metadata', text=description, subject=subject)
 
+    @classmethod
+    def new(cls, **kw):
+        post = cls()
+        for k, v in kw.iteritems():
+            setattr(post, k, v)
+        post.neighborhood_id = c.project.neighborhood_id
+        post.make_slug()
+        post.commit()
+        M.Thread.new(
+            discussion_id=post.app_config.discussion_id,
+            ref_id=post.index_id(),
+            subject='%s discussion' % post.title)
+        return post
+
     def __json__(self):
         return dict(super(BlogPost, self).__json__(),
+                    author=self.author().username,
                     title=self.title,
                     url=h.absurl('/rest' + self.url()),
                     text=self.text,

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e23020d0/ForgeBlog/forgeblog/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/tests/functional/test_rest.py b/ForgeBlog/forgeblog/tests/functional/test_rest.py
index 5addcde..0ab05c2 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_rest.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_rest.py
@@ -43,15 +43,16 @@ class TestBlogApi(TestRestApiBase):
             'labels': 'label1, label2'
         }
         r = self.api_post('/rest/p/test/blog/', **data)
-        assert_equal(r.status_int, 200)
+        assert_equal(r.status_int, 201)
         url = '/rest' + BM.BlogPost.query.find().first().url()
         r = self.api_get('/rest/p/test/blog/')
         assert_equal(r.json['posts'][0]['title'], 'test')
         assert_equal(r.json['posts'][0]['url'], h.absurl(url))
 
         r = self.api_get(url)
-        assert_equal(r.json['title'], 'test')
+        assert_equal(r.json['title'], data['title'])
         assert_equal(r.json['text'], data['text'])
+        assert_equal(r.json['author'], 'test-admin')
         assert_equal(r.json['state'], data['state'])
         assert_equal(r.json['labels'], data['labels'].split(','))
 
@@ -63,7 +64,7 @@ class TestBlogApi(TestRestApiBase):
             'labels': 'label1, label2'
         }
         r = self.api_post('/rest/p/test/blog/', **data)
-        assert_equal(r.status_int, 200)
+        assert_equal(r.status_int, 201)
         url = '/rest' + BM.BlogPost.query.find().first().url()
         data = {
             'text': 'test text2',
@@ -84,7 +85,7 @@ class TestBlogApi(TestRestApiBase):
             'labels': 'label1, label2'
         }
         r = self.api_post('/rest/p/test/blog/', **data)
-        assert_equal(r.status_int, 200)
+        assert_equal(r.status_int, 201)
         url = '/rest' + BM.BlogPost.query.find().first().url()
         self.api_post(url, delete='')
         r = self.api_get(url)
@@ -119,7 +120,7 @@ class TestBlogApi(TestRestApiBase):
         self.app.post('/rest/p/test/blog/',
                       params=dict(title='test', text='test text', state='published'),
                       extra_environ={'username': '*anonymous'},
-                      status=200)
+                      status=201)
 
     def test_update_post_permissons(self):
         self.api_post('/rest/p/test/blog/', title='test', text='test text', state='published')
@@ -145,7 +146,7 @@ class TestBlogApi(TestRestApiBase):
     def test_permission_draft_post(self):
         self.api_post('/rest/p/test/blog/', title='test', text='test text', state='draft')
         r = self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'})
-        assert_equal(r.json, {'posts': []})
+        assert_equal(r.json['posts'], [])
         url = '/rest' + BM.BlogPost.query.find().first().url()
         self.app.post(url.encode('utf-8'),
                       params=dict(title='test2', text='test text2', state='published'),
@@ -162,8 +163,29 @@ class TestBlogApi(TestRestApiBase):
     def test_draft_post(self):
         self.api_post('/rest/p/test/blog/', title='test', text='test text', state='draft')
         r = self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'})
-        assert_equal(r.json, {'posts': []})
+        assert_equal(r.json['posts'], [])
         url = '/rest' + BM.BlogPost.query.find().first().url()
         self.api_post(url, state='published')
         r = self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'})
         assert_equal(r.json['posts'][0]['title'], 'test')
+
+    def test_pagination(self):
+        self.api_post('/rest/p/test/blog/', title='test1', text='test text1', state='published')
+        self.api_post('/rest/p/test/blog/', title='test2', text='test text2', state='published')
+        self.api_post('/rest/p/test/blog/', title='test3', text='test text3', state='published')
+        r = self.api_get('/rest/p/test/blog/', limit='1', page='0')
+        assert_equal(r.json['posts'][0]['title'], 'test3')
+        assert_equal(r.json['count'], 3)
+        assert_equal(r.json['limit'], 1)
+        assert_equal(r.json['page'], 0)
+        r = self.api_get('/rest/p/test/blog/', limit='2', page='0')
+        assert_equal(r.json['posts'][0]['title'], 'test3')
+        assert_equal(r.json['posts'][1]['title'], 'test2')
+        assert_equal(r.json['count'], 3)
+        assert_equal(r.json['limit'], 2)
+        assert_equal(r.json['page'], 0)
+        r = self.api_get('/rest/p/test/blog/', limit='1', page='2')
+        assert_equal(r.json['posts'][0]['title'], 'test1')
+        assert_equal(r.json['count'], 3)
+        assert_equal(r.json['limit'], 1)
+        assert_equal(r.json['page'], 2)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e23020d0/ForgeBlog/forgeblog/tests/unit/test_blog_post.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/tests/unit/test_blog_post.py b/ForgeBlog/forgeblog/tests/unit/test_blog_post.py
index c915278..919ac14 100644
--- a/ForgeBlog/forgeblog/tests/unit/test_blog_post.py
+++ b/ForgeBlog/forgeblog/tests/unit/test_blog_post.py
@@ -26,6 +26,15 @@ from allura.model import Feed
 def wrapped(s):
     return '<div class="markdown_content"><p>%s</p></div>' % s
 
+
+class TestBlogPost(BlogTestWithModel):
+    def test_new(self):
+        post = M.BlogPost.new(title='test', text='test message', state='published')
+        assert_equal(post.title, 'test')
+        assert_equal(post.text, 'test message')
+        assert_equal(post.state, 'published')
+
+
 class TestFeed(BlogTestWithModel):
     def testd(self):
         post = M.BlogPost()


[43/50] git commit: Revert "[#5326] avoid "Broken pipe" error we were seeing, by providing ErrorMiddleware with an explicit wsgi.errors stream"

Posted by jo...@apache.org.
Revert "[#5326] avoid "Broken pipe" error we were seeing, by providing ErrorMiddleware with an explicit wsgi.errors stream"

Taskd doesn't use errormiddleware since 8e3c5c3

This reverts commit 7eb795608877f0b358aa5234b08dcaccabdee0f8.

Conflicts:
	Allura/allura/command/taskd.py


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

Branch: refs/heads/cj/6422
Commit: 2b78927625073acf3f5463065d0e7e4a5759668d
Parents: dbffe37
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Wed Aug 7 20:29:10 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 20:59:25 2013 +0000

----------------------------------------------------------------------
 Allura/allura/command/taskd.py | 8 --------
 1 file changed, 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2b789276/Allura/allura/command/taskd.py
----------------------------------------------------------------------
diff --git a/Allura/allura/command/taskd.py b/Allura/allura/command/taskd.py
index 75d5fcd..50402f0 100644
--- a/Allura/allura/command/taskd.py
+++ b/Allura/allura/command/taskd.py
@@ -98,11 +98,6 @@ class TaskdCommand(base.Command):
         if only:
             only = only.split(',')
 
-        # errors get logged via regular logging and also recorded into the mongo task record
-        # so this is generally not needed, and only present to avoid errors within
-        # weberror's ErrorMiddleware if the default error stream (stderr?) doesn't work
-        wsgi_error_log = open(pylons.config.get('taskd.wsgi_log', '/dev/null'), 'a')
-
         def start_response(status, headers, exc_info=None):
             pass
 
@@ -143,7 +138,6 @@ class TaskdCommand(base.Command):
                             # Build the (fake) request
                             r = Request.blank('/--%s--/%s/' % (self.task.task_name, self.task._id),
                                               {'task': self.task,
-                                               'wsgi.errors': wsgi_error_log,  # ErrorMiddleware records error details here
                                                })
                             list(wsgi_app(r.environ, start_response))
                             self.task = None
@@ -153,8 +147,6 @@ class TaskdCommand(base.Command):
                     time.sleep(10)
                 else:
                     base.log.exception('taskd error %s' % e)
-            finally:
-                wsgi_error_log.flush()
         base.log.info('taskd pid %s stopping gracefully.' % os.getpid())
 
         if self.restart_when_done:


[06/50] git commit: [#6441] ticket:398 refactored hyperlinks in tickets imported from trac

Posted by jo...@apache.org.
[#6441]  ticket:398 refactored hyperlinks in tickets imported from trac


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

Branch: refs/heads/cj/6422
Commit: 70ee25a6fe029c6443d9aca632bcf9f1972a2fb0
Parents: b3fea69
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Mon Jul 22 15:09:42 2013 +0400
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 12:21:14 2013 +0000

----------------------------------------------------------------------
 ForgeTracker/forgetracker/import_support.py         | 16 +++++++++++++---
 .../forgetracker/tests/functional/data/sf.json      |  4 ++--
 .../forgetracker/tests/functional/test_import.py    | 12 +++++++-----
 3 files changed, 22 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/70ee25a6/ForgeTracker/forgetracker/import_support.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/import_support.py b/ForgeTracker/forgetracker/import_support.py
index 268f727..8735f40 100644
--- a/ForgeTracker/forgetracker/import_support.py
+++ b/ForgeTracker/forgetracker/import_support.py
@@ -193,6 +193,7 @@ class ImportSupport(object):
                 new_f, conv = transform
                 remapped[new_f] = conv(v)
 
+        remapped['description'] = self.link_processing(remapped['description'])
         ticket_num = ticket_dict['id']
         existing_ticket = TM.Ticket.query.get(app_config_id=c.app.config._id,
                                           ticket_num=ticket_num)
@@ -215,6 +216,9 @@ class ImportSupport(object):
     def ticket_link(self, m):
         return '(%s)' % m.groups()[0]
 
+    def ticket_bracket_link(self, m):
+        return '[#%s]' % m.groups()[0]
+
     def get_slug_by_id(self, ticket, comment):
         comment = int(comment)
         ticket = TM.Ticket.query.get(app_config_id=c.app.config._id,
@@ -231,17 +235,23 @@ class ImportSupport(object):
 
     def comment_link(self, m):
         ticket, comment = m.groups()
-        return '(%s#%s)' % (ticket, self.get_slug_by_id(ticket, comment))
+        slug = self.get_slug_by_id(ticket, comment)
+        if slug:
+            return '(%s#%s)' % (ticket, self.get_slug_by_id(ticket, comment))
+        else:
+            return '\(%s#comment:%s\)' % (ticket, comment)
 
     def brackets_escaping(self, m):
-        return '[%s]' % m.groups()[0]
+        return '[\[%s\]]' % m.groups()[0]
 
     def link_processing(self, text):
+        short_link_ticket_pattern = re.compile('(?<!\[)#(\d+)(?!\])')
         comment_pattern = re.compile('\(\S*/(\d+)#comment:(\d+)\)')
         ticket_pattern = re.compile('(?<=\])\(\S*ticket/(\d+)\)')
         brackets_pattern = re.compile('\[\[(.*)\]\]')
 
-        text = comment_pattern.sub(self.comment_link, text.replace('\n', ''))
+        text = short_link_ticket_pattern.sub(self.ticket_bracket_link, text)
+        text = comment_pattern.sub(self.comment_link, text)
         text = ticket_pattern.sub(self.ticket_link, text)
         text = brackets_pattern.sub(self.brackets_escaping, text)
         return text

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/70ee25a6/ForgeTracker/forgetracker/tests/functional/data/sf.json
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/data/sf.json b/ForgeTracker/forgetracker/tests/functional/data/sf.json
index 5a11379..82d7b89 100644
--- a/ForgeTracker/forgetracker/tests/functional/data/sf.json
+++ b/ForgeTracker/forgetracker/tests/functional/data/sf.json
@@ -44,14 +44,14 @@
             },
             {
               "class": "COMMENT",
-              "comment": "test link [[2496]](http://testlink.com)  test ticket ([#201](http://sourceforge.net/apps/trac/sourceforge/ticket/201)) \n [test comment](http://sourceforge.net/apps/trac/sourceforge/ticket/204#comment:1)",
+              "comment": "test link [[2496]](http://testlink.com)  test ticket ([#201](http://sourceforge.net/apps/trac/sourceforge/ticket/201)) \n [test comment](http://sourceforge.net/apps/trac/sourceforge/ticket/204#comment:1) \n [test comment](http://sourceforge.net/apps/trac/sourceforge/ticket/204#comment:45)",
               "date": "2009-07-21T15:44:32Z",
               "submitter": "ctsai"
             }
           ], 
           "date": "2009-04-13T08:49:13Z", 
           "date_updated": "2009-07-20T15:44:32Z", 
-          "description": "This problem occurs with IE 7, Windows Vista:\r\nOn the project's public info page (for example:\r\nhttps://sourceforge.net/project/admin/public_info.php?group_id=258655), the text boxes next to \"Descriptive Name\" and \"Project Description\" are not aligned properly; see the screenshot attached. ", 
+          "description": "This problem occurs with IE 7, Windows Vista:\r\nOn the project's public info page (for example:\r\nhttps://sourceforge.net/project/admin/public_info.php?group_id=258655), the text boxes next to \"Descriptive Name\" and \"Project Description\" are not aligned properly; see the screenshot attached. ",
           "id": 204, 
           "keywords": "ENGR",
           "milestone": "test_milestone",

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/70ee25a6/ForgeTracker/forgetracker/tests/functional/test_import.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/test_import.py b/ForgeTracker/forgetracker/tests/functional/test_import.py
index 202c757..1fc7331 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_import.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_import.py
@@ -173,12 +173,14 @@ class TestImportController(TestRestApiBase):
         import_support = ImportSupport()
         result = import_support.link_processing('''test link [[2496]](http://testlink.com)
                                        test ticket ([#201](http://sourceforge.net/apps/trac/sourceforge/ticket/201))
-                                       [test comment](http://sourceforge.net/apps/trac/sourceforge/ticket/204#comment:1)''')
+                                       [test comment](http://sourceforge.net/apps/trac/sourceforge/ticket/204#comment:1)
+                                       #200''')
 
-        assert "test link [2496](http://testlink.com)" in result
-        assert '[test comment](204#)' in result
-        assert 'test link [2496](http://testlink.com)' in result
+        assert "test link [\[2496\]](http://testlink.com)" in result
+        assert '[test comment]\(204#comment:1\)' in result
+        assert 'test link [\[2496\]](http://testlink.com)' in result
         assert 'test ticket ([#201](201))' in result
+        assert '[#200]' in result
 
     @td.with_tracker
     def test_links(self):
@@ -200,7 +202,7 @@ class TestImportController(TestRestApiBase):
             status={'$in': ['ok', 'pending']})).sort('timestamp').all()[0].slug
 
         assert '[test comment](204#%s)' % slug in r
-        assert 'test link [2496](http://testlink.com)' in r
+        assert 'test link [\[2496\]](http://testlink.com)' in r
         assert 'test ticket ([#201](201))' in r
 
     @td.with_tracker


[36/50] git commit: [#6480] Moved Trac wiki importer to separate package

Posted by jo...@apache.org.
[#6480] Moved Trac wiki importer to separate package

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/04cd1ed2
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/04cd1ed2
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/04cd1ed2

Branch: refs/heads/cj/6422
Commit: 04cd1ed28c73fd25791d4fa6a0ad32a5fec2f157
Parents: 85c29b9
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Wed Aug 7 14:27:44 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/project.py |   2 +-
 .../trac/templates/wiki/index.html              |  42 -------
 .../forgeimporters/trac/tests/test_wiki.py      | 104 -----------------
 ForgeImporters/forgeimporters/trac/wiki.py      | 117 -------------------
 ForgeImporters/setup.py                         |   1 -
 5 files changed, 1 insertion(+), 265 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04cd1ed2/ForgeImporters/forgeimporters/google/project.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/project.py b/ForgeImporters/forgeimporters/google/project.py
index dda41ea..e69ac9e 100644
--- a/ForgeImporters/forgeimporters/google/project.py
+++ b/ForgeImporters/forgeimporters/google/project.py
@@ -53,7 +53,7 @@ class GoogleCodeProjectImporter(base.ProjectImporter):
 
     def after_project_create(self, project, **kw):
         project.set_tool_data('google-code', project_name=project.name)
-        tasks.import_project_info.post()
+        tasks.import_project_info.post(project.name)
 
     @with_trailing_slash
     @expose(index_template)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04cd1ed2/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/templates/wiki/index.html b/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
deleted file mode 100644
index 6083b9c..0000000
--- a/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
+++ /dev/null
@@ -1,42 +0,0 @@
-{#-
-       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.
--#}
-{% extends g.theme.master %}
-
-{% block title %}
-{{c.project.name}} / Import Trac Wiki
-{% endblock %}
-
-{% block header %}
-Import wiki from Trac
-{% endblock %}
-
-{% block content %}
-<form action="create" method="post" class="pad">
-  <label for="trac_url">URL of the Trac instance</label>
-  <input name="trac_url" />
-
-  <label for="mount_label">Label</label>
-  <input name="mount_label" value="Source" />
-
-  <label for="mount_point">Mount Point</label>
-  <input name="mount_point" value="source" />
-
-  <input type="submit" />
-</form>
-{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04cd1ed2/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
deleted file mode 100644
index 738e49b..0000000
--- a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
+++ /dev/null
@@ -1,104 +0,0 @@
-#       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
-from mock import Mock, patch
-
-from allura.tests import TestController
-from allura.tests.decorators import with_wiki
-
-from forgeimporters.trac.wiki import (
-    TracWikiImporter,
-    TracWikiImportController,
-    )
-
-
-class TestWikiTicketImporter(TestCase):
-    @patch('forgeimporters.trac.wiki.session')
-    @patch('forgeimporters.trac.wiki.tempfile.NamedTemporaryFile')
-    @patch('forgeimporters.trac.wiki.g')
-    @patch('forgeimporters.trac.wiki.WikiFromTrac')
-    @patch('forgeimporters.trac.wiki.load_data')
-    @patch('forgeimporters.trac.wiki.argparse.Namespace')
-    @patch('forgeimporters.trac.wiki.WikiExporter')
-    @patch('forgeimporters.trac.wiki.ApiTicket')
-    @patch('forgeimporters.trac.wiki.datetime')
-    def test_import_tool(self, dt, ApiTicket, WikiExporter, Namespace,
-            load_data, WikiFromTrac, g, NamedTemporaryFile, session):
-        from datetime import datetime, timedelta
-        now = datetime.utcnow()
-        dt.utcnow.return_value = now
-        export_file = NamedTemporaryFile.return_value.__enter__.return_value
-        export_file.name = '/my/file'
-
-        importer = TracWikiImporter()
-        app = Mock(name='ForgeWikiApp')
-        project = Mock(name='Project', shortname='myproject')
-        project.install_app.return_value = app
-        user = Mock(name='User', _id='id')
-        res = importer.import_tool(project, user,
-                mount_point='pages',
-                mount_label='Pages',
-                trac_url='http://example.com/trac/url')
-        self.assertEqual(res, app)
-        project.install_app.assert_called_once_with(
-                'Wiki', mount_point='pages', mount_label='Pages')
-        ApiTicket.assert_called_once_with(
-                user_id=user._id,
-                capabilities={"import": ["Projects", "myproject"]},
-                expires=now + timedelta(minutes=60))
-        WikiExporter.assert_called_once_with('http://example.com/trac/url/',
-                Namespace.return_value)
-        WikiExporter.return_value.export.assert_called_once_with(export_file)
-        load_data.assert_called_once_with('/my/file',
-                WikiFromTrac.parser.return_value, Namespace.return_value)
-        g.post_event.assert_called_once_with('project_updated')
-
-
-class TestTracWikiImportController(TestController, TestCase):
-    def setUp(self):
-        """Mount Trac import controller on the Wiki admin controller"""
-        super(self.__class__, self).setUp()
-        from forgewiki.wiki_main import WikiAdminController
-        WikiAdminController._importer = TracWikiImportController()
-
-    @with_wiki
-    def test_index(self):
-        r = self.app.get('/p/test/admin/wiki/_importer/')
-        self.assertIsNotNone(r.html.find(attrs=dict(name="trac_url")))
-        self.assertIsNotNone(r.html.find(attrs=dict(name="mount_label")))
-        self.assertIsNotNone(r.html.find(attrs=dict(name="mount_point")))
-
-    @with_wiki
-    @patch('forgeimporters.trac.wiki.TracWikiImporter')
-    def test_create(self, importer):
-        from allura import model as M
-        importer = importer.return_value
-        importer.import_tool.return_value = Mock()
-        importer.import_tool.return_value.url.return_value = '/p/test/mymount'
-        params = dict(trac_url='http://example.com/trac/url',
-                mount_label='mylabel',
-                mount_point='mymount',
-                )
-        r = self.app.post('/p/test/admin/wiki/_importer/create', params,
-                status=302)
-        project = M.Project.query.get(shortname='test')
-        self.assertEqual(r.location, 'http://localhost/p/test/mymount')
-        self.assertEqual(project._id, importer.import_tool.call_args[0][0]._id)
-        self.assertEqual(u'mymount', importer.import_tool.call_args[1]['mount_point'])
-        self.assertEqual(u'mylabel', importer.import_tool.call_args[1]['mount_label'])
-        self.assertEqual(u'http://example.com/trac/url', importer.import_tool.call_args[1]['trac_url'])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04cd1ed2/ForgeImporters/forgeimporters/trac/wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/wiki.py b/ForgeImporters/forgeimporters/trac/wiki.py
deleted file mode 100644
index 300b476..0000000
--- a/ForgeImporters/forgeimporters/trac/wiki.py
+++ /dev/null
@@ -1,117 +0,0 @@
-#       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.
-
-import argparse
-from datetime import (
-        datetime,
-        timedelta,
-        )
-import tempfile
-
-import formencode as fe
-from formencode import validators as fev
-
-from ming.orm import session
-from pylons import tmpl_context as c
-from pylons import app_globals as g
-from tg import (
-        config,
-        expose,
-        redirect,
-        validate,
-        )
-from tg.decorators import (
-        with_trailing_slash,
-        without_trailing_slash,
-        )
-
-from allura.controllers import BaseController
-from allura.lib.decorators import require_post
-from allura.model import ApiTicket
-
-from forgeimporters.base import ToolImporter
-
-from forgewiki.scripts.wiki_from_trac.extractors import WikiExporter
-from forgewiki.scripts.wiki_from_trac.loaders import load_data
-from forgewiki.scripts.wiki_from_trac.wiki_from_trac import WikiFromTrac
-from forgewiki.wiki_main import ForgeWikiApp
-
-
-class TracWikiImportSchema(fe.Schema):
-    trac_url = fev.URL(not_empty=True)
-    mount_point = fev.UnicodeString()
-    mount_label = fev.UnicodeString()
-
-
-class TracWikiImportController(BaseController):
-    @with_trailing_slash
-    @expose('jinja:forgeimporters.trac:templates/wiki/index.html')
-    def index(self, **kw):
-        return {}
-
-    @without_trailing_slash
-    @expose()
-    @require_post()
-    @validate(TracWikiImportSchema(), error_handler=index)
-    def create(self, trac_url, mount_point, mount_label, **kw):
-        app = TracWikiImporter().import_tool(c.project, c.user,
-                mount_point=mount_point,
-                mount_label=mount_label,
-                trac_url=trac_url)
-        redirect(app.url())
-
-
-class TracWikiImporter(ToolImporter):
-    target_app = ForgeWikiApp
-    source = 'Trac'
-    controller = TracWikiImportController
-    tool_label = 'Trac Wiki Importer'
-    tool_description = 'Import your wiki from Trac'
-
-    def import_tool(self, project, user, project_name=None, mount_point=None,
-            mount_label=None, trac_url=None, **kw):
-        """ Import Trac wiki into a new Allura Wiki tool.
-
-        """
-        trac_url = trac_url.rstrip('/') + '/'
-        mount_point = mount_point or 'wiki'
-        app = project.install_app(
-                'Wiki',
-                mount_point=mount_point,
-                mount_label=mount_label or 'Wiki',
-                )
-        session(app.config).flush(app.config)
-        api_ticket = ApiTicket(user_id=user._id,
-                capabilities={"import": ["Projects", project.shortname]},
-                expires=datetime.utcnow() + timedelta(minutes=60))
-        session(api_ticket).flush(api_ticket)
-        options = argparse.Namespace()
-        options.api_key = api_ticket.api_key
-        options.secret_key = api_ticket.secret_key
-        options.project = project.shortname
-        options.wiki = mount_point
-        options.base_url = config['base_url']
-        options.verbose = False
-        options.converter = 'html2text'
-        options.import_opts = []
-        options.user_map_file = None
-        with tempfile.NamedTemporaryFile() as f:
-            WikiExporter(trac_url, options).export(f)
-            f.flush()
-            load_data(f.name, WikiFromTrac.parser(), options)
-        g.post_event('project_updated')
-        return app

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04cd1ed2/ForgeImporters/setup.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/setup.py b/ForgeImporters/setup.py
index c4776ba..b19fd17 100644
--- a/ForgeImporters/setup.py
+++ b/ForgeImporters/setup.py
@@ -41,5 +41,4 @@ setup(name='ForgeImporters',
       google-code-tracker = forgeimporters.google.tracker:GoogleCodeTrackerImporter
       google-code-repo = forgeimporters.google.code:GoogleRepoImporter
       trac-tickets = forgeimporters.trac.tickets:TracTicketImporter
-      trac-wiki = forgeimporters.trac.wiki:TracWikiImporter
       """,)


[33/50] git commit: [#6480] Add tests for Trac ticket importer

Posted by jo...@apache.org.
[#6480] Add tests for Trac ticket importer

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/8a365f74
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/8a365f74
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/8a365f74

Branch: refs/heads/cj/6422
Commit: 8a365f74d9e8921e13a597fe34032b373ee49257
Parents: 1d5708d
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 21:12:10 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/google/tests/__init__.py     |  17 ++++
 .../forgeimporters/trac/tests/__init__.py       |  17 ++++
 .../forgeimporters/trac/tests/test_tickets.py   | 100 +++++++++++++++++++
 .../forgeimporters/trac/tests/test_wiki.py      |  17 ++++
 4 files changed, 151 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8a365f74/ForgeImporters/forgeimporters/google/tests/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tests/__init__.py b/ForgeImporters/forgeimporters/google/tests/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeImporters/forgeimporters/google/tests/__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/8a365f74/ForgeImporters/forgeimporters/trac/tests/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/__init__.py b/ForgeImporters/forgeimporters/trac/tests/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/tests/__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/8a365f74/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
new file mode 100644
index 0000000..8b102a9
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -0,0 +1,100 @@
+#       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
+from mock import Mock, patch
+
+from allura.tests import TestController
+from allura.tests.decorators import with_tracker
+
+from forgeimporters.trac.tickets import (
+    TracTicketImporter,
+    TracTicketImportController,
+    )
+
+
+class TestTracTicketImporter(TestCase):
+    @patch('forgeimporters.trac.tickets.session')
+    @patch('forgeimporters.trac.tickets.g')
+    @patch('forgeimporters.trac.tickets.import_tracker')
+    @patch('forgeimporters.trac.tickets.AlluraImportApiClient')
+    @patch('forgeimporters.trac.tickets.datetime')
+    @patch('forgeimporters.trac.tickets.ApiTicket')
+    @patch('forgeimporters.trac.tickets.TracExport')
+    def test_import_tool(self, TracExport, ApiTicket, dt, ApiClient, import_tracker, g, session):
+        from datetime import datetime, timedelta
+        now = datetime.utcnow()
+        dt.utcnow.return_value = now
+
+        importer = TracTicketImporter()
+        app = Mock(name='ForgeTrackerApp')
+        project = Mock(name='Project', shortname='myproject')
+        project.install_app.return_value = app
+        user = Mock(name='User', _id='id')
+        res = importer.import_tool(project=project,
+                mount_point='bugs',
+                mount_label='Bugs',
+                trac_url='http://example.com/trac/url',
+                user=user)
+        self.assertEqual(res, app)
+        project.install_app.assert_called_once_with(
+                'Tickets', mount_point='bugs', mount_label='Bugs')
+        TracExport.return_value = []
+        TracExport.assert_called_once_with('http://example.com/trac/url/')
+        ApiTicket.assert_called_once_with(
+                user_id=user._id,
+                capabilities={"import": ["Projects", "myproject"]},
+                expires=now + timedelta(minutes=60))
+        api_client = ApiClient.return_value
+        import_tracker.assert_called_once_with(
+                api_client, 'myproject', 'bugs', {}, '[]',
+                validate=False)
+        g.post_event.assert_called_once_with('project_updated')
+
+
+class TestTracTicketImportController(TestController, TestCase):
+    def setUp(self):
+        """Mount Trac import controller on the Tracker admin controller"""
+        super(TestTracTicketImportController, self).setUp()
+        from forgetracker.tracker_main import TrackerAdminController
+        TrackerAdminController._importer = TracTicketImportController()
+
+    @with_tracker
+    def test_index(self):
+        r = self.app.get('/p/test/admin/bugs/_importer/')
+        self.assertIsNotNone(r.html.find(attrs=dict(name="trac_url")))
+        self.assertIsNotNone(r.html.find(attrs=dict(name="mount_label")))
+        self.assertIsNotNone(r.html.find(attrs=dict(name="mount_point")))
+
+    @with_tracker
+    @patch('forgeimporters.trac.tickets.TracTicketImporter')
+    def test_create(self, importer):
+        from allura import model as M
+        importer.import_tool.return_value = Mock()
+        importer.import_tool.return_value.url.return_value = '/p/test/mymount'
+        params = dict(trac_url='http://example.com/trac/url',
+                mount_label='mylabel',
+                mount_point='mymount',
+                )
+        r = self.app.post('/p/test/admin/bugs/_importer/create', params,
+                status=302)
+        project = M.Project.query.get(shortname='test')
+        self.assertEqual(r.location, 'http://localhost/p/test/mymount')
+        self.assertEqual(project._id, importer.import_tool.call_args[0][0]._id)
+        self.assertEqual(u'mymount', importer.import_tool.call_args[1]['mount_point'])
+        self.assertEqual(u'mylabel', importer.import_tool.call_args[1]['mount_label'])
+        self.assertEqual(u'http://example.com/trac/url', importer.import_tool.call_args[1]['trac_url'])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8a365f74/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/tests/test_wiki.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.
+


[27/50] git commit: [#6461] remove gdata dep since it'll be replaced soon anyway

Posted by jo...@apache.org.
[#6461] remove gdata dep since it'll be replaced soon anyway


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

Branch: refs/heads/cj/6422
Commit: fb06265590066cd8748ae1355ec12db2b113c7dc
Parents: 9cc5c8f
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Tue Aug 6 21:41:24 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Tue Aug 6 21:49:17 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/tracker.py | 2 +-
 requirements-common.txt                         | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/fb062655/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 602690e..4bc602c 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -19,7 +19,7 @@ from collections import defaultdict
 from datetime import datetime
 
 from pylons import tmpl_context as c
-import gdata
+#import gdata
 from ming.orm import session
 
 from allura.lib import helpers as h

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/fb062655/requirements-common.txt
----------------------------------------------------------------------
diff --git a/requirements-common.txt b/requirements-common.txt
index e7b7ef1..5e261a0 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -50,7 +50,6 @@ TurboGears2==2.1.5
 WebOb==1.0.8
 # part of the stdlib, but with a version number.  see http://guide.python-distribute.org/pip.html#listing-installed-packages
 wsgiref==0.1.2
-gdata==2.0.18
 
 # tg2 deps (not used directly)
 Babel==0.9.6


[50/50] git commit: [#6422] Added KEYS file for ASF release

Posted by jo...@apache.org.
[#6422] Added KEYS file for ASF release

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/387f1cf9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/387f1cf9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/387f1cf9

Branch: refs/heads/cj/6422
Commit: 387f1cf9b6cdc2c3527ea4aa9e282f3c8d6384e8
Parents: 3cd0160
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Mon Aug 12 16:40:17 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Mon Aug 12 16:40:17 2013 +0000

----------------------------------------------------------------------
 KEYS | 515 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 515 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/387f1cf9/KEYS
----------------------------------------------------------------------
diff --git a/KEYS b/KEYS
new file mode 100644
index 0000000..d7b2fe7
--- /dev/null
+++ b/KEYS
@@ -0,0 +1,515 @@
+This file contains the PGP keys of various developers.
+
+Users: pgp < KEYS
+or
+       gpg --import KEYS
+Developers: 
+    pgp -kxa <your name> and append it to this file.
+or
+    (pgpk -ll <your name> && pgpk -xa <your name>) >> this file.
+or
+    (gpg --list-sigs <your name>
+    && gpg --armor --export <your name>) >> this file.
+
+pub   1024D/9BB3CE70 2003-03-21
+uid                  Dave Brondsema <da...@brondsema.net>
+sig 3        F2454727 2005-02-21  Andrew Schamp <sc...@gmail.com>
+sig          E4188F13 2004-10-07  Mark Hepler (Work Email account) <mh...@wrgservices.com>
+sig 2        04C18E05 2003-04-05  Christopher TenHarmsel (March, 2003) <ch...@staticmethod.net>
+sig 2        22349247 2004-05-18  Matthew Post <ma...@onpost.net>
+sig 2        A871C05F 2004-10-03  Richard Weait <ri...@weait.com>
+sig 3        74FF6C22 2004-10-02  Richard Raymond Clark <rr...@rrclark.net>
+sig 3        810CC15E 2004-10-02  Brian Pepple <bp...@fedoraproject.org>
+sig 3        E2E52E83 2004-10-03  Josiah Royse <jo...@yahoo.com>
+sig 3        CC78C893 2004-10-03  Rich Bowen <rb...@rcbowen.com>
+sig 3        AC17B5A8 2004-10-04  David Heck <sa...@yahoo.com>
+sig 3        1DD888B4 2004-10-05  Dane Miller (daneturner) <da...@olneyfriends.org>
+sig 3        46CBA65E 2004-10-10  Ross Gardler (Apache) <rg...@apache.org>
+sig 3        D798EFD2 2004-10-25  Jeremy D. Frens <jd...@calvin.edu>
+sig 3        407CDD8D 2004-11-12  Santiago Gala (nostromo) <sg...@hisitech.com>
+sig 3        ADBF9E1A 2004-11-12  Santiago Gala (Santiago Juan Gala Perez) <sg...@apache.org>
+sig 3        5DCB26CC 2005-03-04  Chris Crowe <ct...@calvin.edu>
+sig 3        80A795A2 2005-07-31  Phil & Carol Brondsema <br...@yahoo.com>
+sig 3        9BB3CE70 2003-03-21  Dave Brondsema <da...@brondsema.net>
+sig 3        9BB3CE70 2003-11-16  Dave Brondsema <da...@brondsema.net>
+sig 3        9BB3CE70 2003-03-21  Dave Brondsema <da...@brondsema.net>
+sig 2        5D35E515 2004-10-11  Preacher Dave <pr...@saintmail.net>
+sig 3        4989D25C 2004-10-02  Kevin Gaughen <ke...@gaughenhome.com>
+sig          449C78B1 2013-08-05  Cory Johns <jo...@apache.org>
+uid                  Dave Brondsema <in...@splike.com>
+sig 2        04C18E05 2003-04-05  Christopher TenHarmsel (March, 2003) <ch...@staticmethod.net>
+sig 2        22349247 2004-05-18  Matthew Post <ma...@onpost.net>
+sig 2        A871C05F 2004-10-03  Richard Weait <ri...@weait.com>
+sig 3        74FF6C22 2004-10-02  Richard Raymond Clark <rr...@rrclark.net>
+sig 3        810CC15E 2004-10-02  Brian Pepple <bp...@fedoraproject.org>
+sig 3        E2E52E83 2004-10-03  Josiah Royse <jo...@yahoo.com>
+sig 3        CC78C893 2004-10-03  Rich Bowen <rb...@rcbowen.com>
+sig 3        AC17B5A8 2004-10-04  David Heck <sa...@yahoo.com>
+sig 3        1DD888B4 2004-10-05  Dane Miller (daneturner) <da...@olneyfriends.org>
+sig 3        46CBA65E 2004-10-10  Ross Gardler (Apache) <rg...@apache.org>
+sig 3        D798EFD2 2004-10-25  Jeremy D. Frens <jd...@calvin.edu>
+sig 3        407CDD8D 2004-11-12  Santiago Gala (nostromo) <sg...@hisitech.com>
+sig 3        ADBF9E1A 2004-11-12  Santiago Gala (Santiago Juan Gala Perez) <sg...@apache.org>
+sig 3        F2454727 2005-02-21  Andrew Schamp <sc...@gmail.com>
+sig 3        5DCB26CC 2005-03-04  Chris Crowe <ct...@calvin.edu>
+sig 3        80A795A2 2005-07-31  Phil & Carol Brondsema <br...@yahoo.com>
+sig 3        9BB3CE70 2003-03-21  Dave Brondsema <da...@brondsema.net>
+sig 2        5D35E515 2004-10-11  Preacher Dave <pr...@saintmail.net>
+sig 3        4989D25C 2004-10-02  Kevin Gaughen <ke...@gaughenhome.com>
+sig          449C78B1 2013-08-05  Cory Johns <jo...@apache.org>
+uid                  Dave Brondsema <br...@yahoo.com>
+sig 2        04C18E05 2003-04-05  Christopher TenHarmsel (March, 2003) <ch...@staticmethod.net>
+sig 2        22349247 2004-05-18  Matthew Post <ma...@onpost.net>
+sig 2        A871C05F 2004-10-03  Richard Weait <ri...@weait.com>
+sig 3        74FF6C22 2004-10-02  Richard Raymond Clark <rr...@rrclark.net>
+sig 3        810CC15E 2004-10-02  Brian Pepple <bp...@fedoraproject.org>
+sig 3        E2E52E83 2004-10-03  Josiah Royse <jo...@yahoo.com>
+sig 3        CC78C893 2004-10-03  Rich Bowen <rb...@rcbowen.com>
+sig 3        AC17B5A8 2004-10-04  David Heck <sa...@yahoo.com>
+sig 3        1DD888B4 2004-10-05  Dane Miller (daneturner) <da...@olneyfriends.org>
+sig 3        46CBA65E 2004-10-10  Ross Gardler (Apache) <rg...@apache.org>
+sig 3        D798EFD2 2004-10-25  Jeremy D. Frens <jd...@calvin.edu>
+sig 3        407CDD8D 2004-11-12  Santiago Gala (nostromo) <sg...@hisitech.com>
+sig 3        ADBF9E1A 2004-11-12  Santiago Gala (Santiago Juan Gala Perez) <sg...@apache.org>
+sig 3        F2454727 2005-02-21  Andrew Schamp <sc...@gmail.com>
+sig 3        5DCB26CC 2005-03-04  Chris Crowe <ct...@calvin.edu>
+sig 3        80A795A2 2005-07-31  Phil & Carol Brondsema <br...@yahoo.com>
+sig 3        9BB3CE70 2003-03-21  Dave Brondsema <da...@brondsema.net>
+sig 2        5D35E515 2004-10-11  Preacher Dave <pr...@saintmail.net>
+sig 3        4989D25C 2004-10-02  Kevin Gaughen <ke...@gaughenhome.com>
+sig          449C78B1 2013-08-05  Cory Johns <jo...@apache.org>
+uid                  Dave Brondsema <br...@apache.org>
+sig 3        D798EFD2 2004-10-25  Jeremy D. Frens <jd...@calvin.edu>
+sig 3        F2454727 2005-02-21  Andrew Schamp <sc...@gmail.com>
+sig 2        22349247 2004-05-18  Matthew Post <ma...@onpost.net>
+sig 2        A871C05F 2004-10-03  Richard Weait <ri...@weait.com>
+sig 3        810CC15E 2004-10-02  Brian Pepple <bp...@fedoraproject.org>
+sig 3        E2E52E83 2004-10-03  Josiah Royse <jo...@yahoo.com>
+sig 3        CC78C893 2004-10-03  Rich Bowen <rb...@rcbowen.com>
+sig 3        AC17B5A8 2004-10-04  David Heck <sa...@yahoo.com>
+sig 3        1DD888B4 2004-10-05  Dane Miller (daneturner) <da...@olneyfriends.org>
+sig 3        46CBA65E 2004-10-10  Ross Gardler (Apache) <rg...@apache.org>
+sig 3        407CDD8D 2004-11-12  Santiago Gala (nostromo) <sg...@hisitech.com>
+sig 3        ADBF9E1A 2004-11-12  Santiago Gala (Santiago Juan Gala Perez) <sg...@apache.org>
+sig 3        5DCB26CC 2005-03-04  Chris Crowe <ct...@calvin.edu>
+sig 3        80A795A2 2005-07-31  Phil & Carol Brondsema <br...@yahoo.com>
+sig 3        9BB3CE70 2003-03-21  Dave Brondsema <da...@brondsema.net>
+sig 3        9BB3CE70 2004-02-27  Dave Brondsema <da...@brondsema.net>
+sig 2        5D35E515 2004-10-11  Preacher Dave <pr...@saintmail.net>
+sig 3        4989D25C 2004-10-02  Kevin Gaughen <ke...@gaughenhome.com>
+sig          449C78B1 2013-08-05  Cory Johns <jo...@apache.org>
+sub   4096R/62EFBB73 2006-09-16
+sig          9BB3CE70 2006-12-17  Dave Brondsema <da...@brondsema.net>
+sub   2048g/F41E6EB8 2003-03-21
+sig          9BB3CE70 2003-03-21  Dave Brondsema <da...@brondsema.net>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.12 (Darwin)
+
+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
+=oAU3
+-----END PGP PUBLIC KEY BLOCK-----
+
+
+pub   4096R/449C78B1 2013-08-05
+uid                  Cory Johns <jo...@apache.org>
+sig 3        449C78B1 2013-08-05  Cory Johns <jo...@apache.org>
+sig 3        9BB3CE70 2013-08-05  Dave Brondsema <da...@brondsema.net>
+sig          1BBEC342 2013-08-12  Tim Van Steenburgh <tv...@apache.org>
+uid                  Cory Johns (CODE SIGNING KEY) <jo...@gmail.com>
+sig 3        449C78B1 2013-08-05  Cory Johns <jo...@apache.org>
+sig 3        9BB3CE70 2013-08-05  Dave Brondsema <da...@brondsema.net>
+sig          1BBEC342 2013-08-12  Tim Van Steenburgh <tv...@apache.org>
+uid                  Cory Johns <cj...@slashdotmedia.com>
+sig 3        449C78B1 2013-08-05  Cory Johns <jo...@apache.org>
+sig 3        9BB3CE70 2013-08-05  Dave Brondsema <da...@brondsema.net>
+sig          1BBEC342 2013-08-12  Tim Van Steenburgh <tv...@apache.org>
+sub   4096R/4A397F0E 2013-08-05
+sig          449C78B1 2013-08-05  Cory Johns <jo...@apache.org>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.12 (Darwin)
+
+mQINBFIAF5sBEADEBSu9TqSi1Miy49oiyGB9yZ0XjLEoMIFChUTbclVbsdgo/D/o
+sp+s/CP0IDms60efScqly2/6JoJh03sAscOcYzovhVI1r6ZxSpAzTo2VWfp1LSTH
+fp71gp4BFE5Q3Dt54c0+eP0BwrtjBTX6F6NXULrRYpUF6AC6dXf2aFWtICM4ikZU
+OVVaN3tZFFbcyFkGUtFXK0UfbSbmDd5YQ09XgpiKOiNX68SOAdRkRIBmAw3Tv9sk
+mvnpFuklo30AykwUXapjmousYr2MXuPcONj2esvM1/BqQS3uW/bq2ssRQ1vAhG4z
+ATOFn+NXCcsJ2cQANG+CXVYYxQVtCB414pc2nj1+NL6Kw1Zk7sSGCcRHyt67jntL
+qahGrvTVWx5MM1GUiUG2eDThhuEvG84+V+BSWloHxaApcRrHB8Me08pn+/tOtXic
+cgB5auyQl/HwDbWLu8QpkUzG8c4kx6wIRNeKAD0G1uTU9NCe/weMhEOXQ6iTTtA1
+P7ex/Vk2P+K1BhyqNzyrlH4c5O5sGZ8G37aHb3t6hNoQYwLS4+m+yyw3LPf3NNLn
+vmydqTOco7fiV2fb7dYQymhnMLnG+uQz8jsGotBj9XQl0Q+2yCQbMXWYeFT6ita7
+/b9hRR9lTgwQvf4auDsUDBbTTbOIVMebyed7vgFs9h0t1ZSkOdf5nYyiWQARAQAB
+tDFDb3J5IEpvaG5zIChDT0RFIFNJR05JTkcgS0VZKSA8am9obnNjYUBnbWFpbC5j
+b20+iQI4BBMBAgAiBQJSABebAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK
+CRDbbgcbRJx4sYvtD/4lCo/SdTEWNMaF2lUdgpLdoMVEh/1spwT322hQyatTAXM1
+u0w+l4kHio0VcVTdArb/49TUnGNp4u+iPvJGTK/0nJqAlzHO7qUVnnX/X5HCZgvD
+1s1Vy1n5v2//rdzOWSZhIFeGjHb6g6gK8jPgFvdRvVreeXH8ly9RlHhyS/22/als
+dVqPlk3XEeuDfcjG4InNqJT515qQNIcxaahaggiqSpCa0+/8F5zjDHMF+/KDEmyw
+YHhNz9n3lnYKSrhXf2alBYZV0JkIjkXhMitYt+S2gdIJ3/R6yhOhDH9wDgLKzRMl
+k5yl5vw+Xtzg4BqEiVRaEJGfkZQK539oG4eFlhBtKlYNY5U2G9pm0slApF8FPuJU
+I/81fhMu8rbl8MzvwzWozQDKgAyIYxqw5/u13IfiJ9x0D+fx7oLBNAl6AaBiAvkq
+d4mjwg4h6/Rz0sVH44cTx0zx43Nx+bEg8yyE+1+cbeB93xweIt76kp192SRhV1fi
+qwfyvOUMlRJXqnsuXOHy+ze9UVk8mA1OLiuJw2N+caouqqz/zC5qVEIFgsENk9NC
+XyoPIIHCjJP71DXzh8u2l3QoZdTElVeI4nguyEWbaabIFrLS9Aicad2OsOSmYVH5
+OvV1p2/1KG2DNEzF25cDVSbBdbeZcXzBSRVtoLVvJYWetKPeGTOumlyz9akalYhG
+BBMRAgAGBQJSABvoAAoJEFbwUm+bs85w1R0AnR/OJncHqkw9MC4c8rpYiVPdNaAV
+AJsEEDVI5D+Qr9cLf/Cp7SIV3HO+U4kCHAQQAQoABgUCUgkLNgAKCRDvFDKfG77D
+QmbJD/9zQuBmNNEEPkTXU26X3mPXVHMLvHdTrkw1V5808QPSbnmhPynHu7IfdveO
+S/++8vYFPJd2hmCeEz3XUdLgDNJBMDh2QCZLdjA92YFZ8//huzVzn8iy903zdRiW
+zc9XzC2Syq8/Shpk97DBZXuSEoWs0e94cBOkLoACzAzUI0UWDxE+7fVWSLlp7udR
+pi5iP/jgsKRc6/RGRa2SCMNxFtC8rn9aXmzYzU4kLMizIzWu1nT5ek8wWFjFZs9n
+RsxFRW8D93CRJ/L4xZ1nqfdeoqRYwVznydpBSxKh7sLyLOObBDz377oOvnVX1vXB
+ScFyPQnLKVIIcCfR2gOVvWco7PTB9KCobwddADoGcSSp6TpFDh+Vu5aRJ9XJwcp6rcgfWREvC1/6ltK1MywIBzPooPt7
+lXX/MWmH93f7mqnSQT7YXAuJGXAjcaTTcrOmafBPOACQtIy3fAr5yfnp+ILWLgoM
+1NWuX5NqTm8fUUpZauMAEHuBC43rce8JukEmFo/tUVz6uQINBFIAF5sBEADy5gR1
+g4OXvvVqq4+UlCCalr4MMgbJvtGe5Ws6g5DWPMk3SSg79T7ZAKv4Mw2f0iIpu4Uc
+7HeJulb5uaJkPNAkTTyHLwfR+DTSUQz0cH+rvFVhqMVTqzko4AdM921Szkau2rNi
+gbRWBYWbusIgjzr46r/k/K3jRWFRirp0hyQIk9IXtgLKSSIXpDuxPsy8vl5UJlEP
+lCpYfFenJXmuEDIWGHYxQuhx+KWVCPRHHHSzAwTsVy2qdYIjEdtZmbTkkWVLdaxK
+g3xMEbDm8sBHxYCMq+BzZ/hiPcDiFKPJUvxSz5X0XUSXBtj1cz2xLJoLKqkC03hZ
+TZN4EMjuafdcIUuokQOxEPylX5nMA6yYtgYza3PxHwKBqMHAD29GcUcSj5NWEiWW
+okYQ8nA2wSI7v2Llh1KXk3uRGxtLjg4QlsDpi6Y64zxPNrOHTsakwgTeyYbWqpMM
+P8M07N/2ctJV9+q69qY0cf2I/tk2ZPtJTle1x1vzq604E3t5r8cKo29w3SM6sA8g
+iZFQPX3nxtG+yH8vVIABFftp3BUkmj8xw4H4eEjaKQJ5TdyJoLZLSoY7B8bSwdaC
+m64wTzP9SMqyXV/VllZ2Leb7vS3ITgpewjgWS2VTGLaTpSfqgi5eBBqyc5HxPnEY
+Z3ONVDTc55GfSAFRytxBsNFR+9vF2QSNd2mFYwARAQABiQIfBBgBAgAJBQJSABeb
+AhsMAAoJENtuBxtEnHixpv4P+gLkzxLQ16N7o1t1XAFsyq+8SpIRMOiNvYkzaOP0
+r4nYWB+drttz8irzF1rhUjmg+XwrOCD8eCgo9ewRJTQPTGGdOUIHHLyQUp85N0GZ
+fYoKZvx8V017oeIZo6YSj63O4CZnJ8lYZhB5qcLNVaLWeTWroBV4D1rCUOVd920V
+ABSmGImQdHJCUdRgpiUsdQqi55pPCS56g8D+rahRPl576CEZlLsx2XG8jYDoDTlC
+S7BFG9qfEnVWYXt0kUvDUF07MjZF6DrU3vLLhWojjk2PjHUcMBv4m2MDajDWfyb+
+CAzuh5zS1oYy1LK7ZXpguKzC8I8LXuh8CclBHFUZXAvDrRYiJf4zeXsAsQzCS1JX
+oHoAZt8gTWu0jLcnUhN/2WGjm0n6tmgm9byzHh5FIdEo+822AmTZVykb+vEl4d52
+/Bsl3GHr5/TciBE4C/AotSk2UCYRFJBlj5OCGl0ohcEKDVNPw7E60ABJuzUQysWs
+4ttZds5x3WDH6P+viT+JhcqaabrIznHYcerirf2AcCu73pUMfe9mNrGh+KzVPexv
+Exsv93eZkPLi6Wgps9GOjUD8qP7vhWsx9K1TwMRccKBaeKeFwCosHGLLpGdf1D1k
+SlPJhUMzZbxLvTaxbBpB5sFE9vqDc8Z8hQpA5DX9rzxeauRq/DqyUiuDv/doWRBJ
+issW
+=O49T
+-----END PGP PUBLIC KEY BLOCK-----
+
+
+pub   4096R/1BBEC342 2013-08-12
+uid                  Tim Van Steenburgh <tv...@apache.org>
+sig 3        1BBEC342 2013-08-12  Tim Van Steenburgh <tv...@apache.org>
+sig          449C78B1 2013-08-12  Cory Johns <jo...@apache.org>
+uid                  Tim Van Steenburgh <tv...@slashdotmedia.com>
+sig 3        1BBEC342 2013-08-12  Tim Van Steenburgh <tv...@apache.org>
+sig          449C78B1 2013-08-12  Cory Johns <jo...@apache.org>
+uid                  Tim Van Steenburgh (CODE SIGNING KEY) <tv...@gmail.com>
+sig 3        1BBEC342 2013-08-12  Tim Van Steenburgh <tv...@apache.org>
+sig          449C78B1 2013-08-12  Cory Johns <jo...@apache.org>
+sub   4096R/8E1161B2 2013-08-12
+sig          1BBEC342 2013-08-12  Tim Van Steenburgh <tv...@apache.org>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.12 (Darwin)
+
+mQINBFIJBhQBEACyLY86OAsunSUMiArtDSgIdObQ3EuoWjM2k/FU6eSNVXwIz+bp
+gwUvVa0gecjLlj8hCPxjXcKIasvtvVNRT2S/ErbN6glZQ+AWrSaRUwPqGj1b5w8Y
+4dwlQjs2cGbMuIpMy5J3LDib7LrzOqJ4lk2j+EFMlOhQ3IeM1Um1nHUnSFSwRjtw
+DjgjNxuPJAJM7ArSoneQtuMhKc1UyTNwdIJ4g5W6ZeT/+CKaewskFqnjmg7DGMdC
+QwVVLtRasP7zPMsolXQGZlfb98Pja9wHeeCWZxxjWFygzxHYpQ5IclM2CwDOA9e6
+GZyGVfxFMYJTKVYlWG4QOswkopmfPTM0zLuWIYxV/uYkKpn4bYQ81jPl4hhIayCR
+7D9NOypgpPi2B1DtSHKEPIUO9Mx0Fj8sYSeKwFhSGnhrXpq3lHC8pzHf+P2+QGwV
+3CPAUyS/oYLKfI8uPQv3Bj9uvU9Jy0RzLPo45HXfhBRYlU1DjHOP/XZn4wF9Gbea
+w4tntxnsWztAx1OG9516DeUGaaTlhHitEVH3xhuVsaz+74sv11IL3LVSPpdG5Vyz
+M6ok+nYHz84Z3SF370IExMb7wq6TCzKUtGcmfgPlTM3ZfRCxTqZGPLiy5WoY1d3W
+yC0GEkCaacLsjrQhCXnBicAjG1MFxzW54wWl57eG3KFe80h/s3Vbqyo03QARAQAB
+tC5UaW0gVmFuIFN0ZWVuYnVyZ2ggPHR2YW5zdGVlbmJ1cmdoQGFwYWNoZS5vcmc+
+iQI3BBMBCgAhBQJSCQbGAhsDBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEO8U
+Mp8bvsNCxGYQAKcoObUka8yZgdneQTKORuf6sammeV1eL2IwNb9bI3+022xashUj
+hZApA7YqSQiiQ8SP0UFy5HRcQvkp/DVCZ24ga4hYicw869BtN708gBcpGUk0dtTE
+5iu1/5z0AR/bIOuvG/yl3Eq3wSlf7OcOwZKnE2Vji2MLBLsGK74Mr39vAmvPHA4k
+PkJ8uChqu4BGlyFJ/VDnaQNBvy0ptR1hFsYcn9o3KgoVUv8Xi1YgCRvTt4lOKt5n
+Csa+ilNl4mTzqfS3rFk6yH/h/TDv9qE14glLhbJCrqIvyMaDtfMQK6SWo+6t4Yct
+fSVNXXW+t6pzGUY+q+uC3F41xH6v6Pr9ZyM8elhIgCpEal/l3dJFo+clWUou63Jn
+e+u6mjUmN8gEQVJrsX4uA25ArCMTv/cFuNFxEtfwaLZTGaCtWVrwAR7cfRs++Zwr
+BjTysuRQ5GstOZosENOO/M950eAlUnKsqR8c8Xb/vN4UkhxxWJGw7m2kSKb5I9su
+N9hjX3NqwcMeg9jneoTFbCifc23WvnucNP1KsCcW5CzTj7eq7MOG8qOd43gjFmJs
+Quu+lsk/HCqgOAQ04teBuWjg7GDX3QCe4Mn1weaq/NrwBVdcQN3tohgVXMHUDOXQ
+TnN2qXK2FmYOztstwuyu0IMgAJZgYYL0E8eX+owFaBEoBqMtQiL49tcyiQIcBBAB
+AgAGBQJSCQv6j1Xo
+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
+dyjaJJ5GThZdiIDBBhbeoqhdZbVvZg==
+=lRDi
+-----END PGP PUBLIC KEY BLOCK-----
+


[09/50] git commit: [#6460] Get root_project of artifact

Posted by jo...@apache.org.
[#6460] Get root_project of artifact

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/625bf2ec
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/625bf2ec
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/625bf2ec

Branch: refs/heads/cj/6422
Commit: 625bf2ec9dfe3765d7d8813324b8a61986aba51f
Parents: 4b2d8c1
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Wed Jul 31 14:18:47 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 14:18:47 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/security.py | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/625bf2ec/Allura/allura/lib/security.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/security.py b/Allura/allura/lib/security.py
index a39e68d..a0497ca 100644
--- a/Allura/allura/lib/security.py
+++ b/Allura/allura/lib/security.py
@@ -287,9 +287,8 @@ def has_access(obj, permission, user=None, project=None):
                 elif isinstance(obj, M.Project):
                     project = obj.root_project
                 else:
-                    project = getattr(obj, 'project', None)
-                    if project is None:
-                        project = c.project.root_project
+                    project = getattr(obj, 'project', None) or c.project
+                    project = project.root_project
             roles = cred.user_roles(user_id=user._id, project_id=project._id).reaching_ids
         chainable_roles = []
         for rid in roles:


[22/50] git commit: [#4931] Add download link for images in repo

Posted by jo...@apache.org.
[#4931] Add download link for images in repo

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/0065ba1e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/0065ba1e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/0065ba1e

Branch: refs/heads/cj/6422
Commit: 0065ba1ec6488dfb70d7d117cc95ade240e09d89
Parents: 4343ee0
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Jul 30 21:31:03 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Jul 31 19:41:12 2013 +0000

----------------------------------------------------------------------
 Allura/allura/templates/repo/file.html | 1 +
 1 file changed, 1 insertion(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/0065ba1e/Allura/allura/templates/repo/file.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/repo/file.html b/Allura/allura/templates/repo/file.html
index 3dcb447..04f7205 100644
--- a/Allura/allura/templates/repo/file.html
+++ b/Allura/allura/templates/repo/file.html
@@ -80,6 +80,7 @@
   {% endif %}
 
   {% if blob.has_image_view %}
+    <p><a href="?format=raw">Download this file</a></p>
     <img src="?format=raw" alt=""/>
   {% elif blob.has_html_view or blob.has_pypeline_view or force_display %}
     <p><a href="?format=raw">Download this file</a></p>


[15/50] git commit: [#6446] ticket:400 do not show not ok posts for other apis

Posted by jo...@apache.org.
[#6446] ticket:400 do not show not ok posts for other apis


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

Branch: refs/heads/cj/6422
Commit: 0877d5c3e1dd3140229f36e7100b1d346b08ffea
Parents: a7e25cc
Author: Anton Kasyanov <mi...@gmail.com>
Authored: Wed Jul 24 11:47:10 2013 +0300
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 14:55:49 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/discuss.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/0877d5c3/Allura/allura/model/discuss.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/discuss.py b/Allura/allura/model/discuss.py
index 97d19d0..ee62778 100644
--- a/Allura/allura/model/discuss.py
+++ b/Allura/allura/model/discuss.py
@@ -169,7 +169,7 @@ class Thread(Artifact, ActivityObject):
                         subject=p.subject,
                         attachments=[dict(bytes=attach.length,
                                           url=h.absurl(attach.url())) for attach in p.attachments])
-                   for p in self.posts])
+                   for p in self.posts if p.status == 'ok'])
 
     @property
     def activity_name(self):


[17/50] git commit: Move requests pkg to common dependencies

Posted by jo...@apache.org.
Move requests pkg to common dependencies

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/868694ec
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/868694ec
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/868694ec

Branch: refs/heads/cj/6422
Commit: 868694ecb4c4471741693e13c6b1033aa6151d79
Parents: 8b994ee
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Wed Jul 31 16:40:53 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 16:40:53 2013 +0000

----------------------------------------------------------------------
 requirements-common.txt   | 1 +
 requirements-optional.txt | 3 ---
 2 files changed, 1 insertion(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/868694ec/requirements-common.txt
----------------------------------------------------------------------
diff --git a/requirements-common.txt b/requirements-common.txt
index 81a470b..5e261a0 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -38,6 +38,7 @@ python-magic==0.4.3
 python-openid==2.2.5
 python-oembed==0.2.1
 pytidylib==0.2.1
+requests==1.2.3
 # for taskd proc name switching
 setproctitle==1.1.7
 # dep of pypeline

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/868694ec/requirements-optional.txt
----------------------------------------------------------------------
diff --git a/requirements-optional.txt b/requirements-optional.txt
index da1f205..accdc43 100644
--- a/requirements-optional.txt
+++ b/requirements-optional.txt
@@ -15,6 +15,3 @@ MySQL-python  # GPL
 # One or the other is required to enable spam checking
 akismet==0.2.0
 PyMollom==0.1  # GPL
-
-# For wiki-export script
-requests


[20/50] git commit: [#6456] Added documentation for ForgeImporters package

Posted by jo...@apache.org.
[#6456] Added documentation for ForgeImporters package

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/227396fa
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/227396fa
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/227396fa

Branch: refs/heads/cj/6422
Commit: 227396fa98c0b7d00fffc52552e30a1198273116
Parents: 4ed7af8
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue Jul 23 22:34:53 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Jul 31 18:44:35 2013 +0000

----------------------------------------------------------------------
 .gitignore                                      |   2 +-
 ForgeImporters/docs/Makefile                    | 105 +++++++++
 ForgeImporters/docs/conf.py                     | 215 +++++++++++++++++++
 ForgeImporters/docs/framework.rst               |  46 ++++
 ForgeImporters/docs/importers/google.rst        |  30 +++
 ForgeImporters/docs/index.rst                   |  50 +++++
 ForgeImporters/docs/make.bat                    | 130 +++++++++++
 ForgeImporters/forgeimporters/base.py           |  60 +++++-
 ForgeImporters/forgeimporters/google/project.py |   7 +
 9 files changed, 642 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/227396fa/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 7b76704..658b4d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,7 +12,7 @@ tags
 *~
 *.swp
 .dbshell
-Allura/docs/_build/*
+*/docs/_build/*
 mail/logs/*
 sandbox-env/*
 download/*

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/227396fa/ForgeImporters/docs/Makefile
----------------------------------------------------------------------
diff --git a/ForgeImporters/docs/Makefile b/ForgeImporters/docs/Makefile
new file mode 100644
index 0000000..b3ca8af
--- /dev/null
+++ b/ForgeImporters/docs/Makefile
@@ -0,0 +1,105 @@
+#       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.
+
+# Makefile for Sphinx documentation
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  dirhtml   to make HTML files named index.html in directories"
+	@echo "  pickle    to make pickle files"
+	@echo "  json      to make JSON files"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  qthelp    to make HTML files and a qthelp project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/allura.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/allura.qhc"
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/227396fa/ForgeImporters/docs/conf.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/docs/conf.py b/ForgeImporters/docs/conf.py
new file mode 100644
index 0000000..07430a5
--- /dev/null
+++ b/ForgeImporters/docs/conf.py
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+
+#       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.
+
+# allura documentation build configuration file, created by
+# sphinx-quickstart on Tue Nov 10 15:32:38 2009.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'Apache Allura (incubating)'
+copyright = '2012-2013 The Apache Software Foundation'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+#version = '0.1'
+# The full version, including alpha/beta/rc tags.
+#release = '0.1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'sphinxdoc'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+html_title = 'ForgeImporters for Allura documentation'
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'alluradoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'allura.tex', u'allura Documentation',
+   u'Cory Johns, Tim Van Steenburgh, Dave Brondsema', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/227396fa/ForgeImporters/docs/framework.rst
----------------------------------------------------------------------
diff --git a/ForgeImporters/docs/framework.rst b/ForgeImporters/docs/framework.rst
new file mode 100644
index 0000000..496f59d
--- /dev/null
+++ b/ForgeImporters/docs/framework.rst
@@ -0,0 +1,46 @@
+..     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.
+
+:mod:`forgeimporters.base`
+==========================
+
+The following classes make up the base framework for
+importers.
+
+These can be used to create additional importers
+for Allura, which can be made available by creating an
+appropriate entry-point under `allura.project_importers` or
+`allura.importers` for project importers or tool importers,
+respectively.
+
+:class:`~forgeimporters.base.ProjectImporter`
+---------------------------------------------
+
+.. autoclass:: forgeimporters.base.ProjectImporter
+   :members:
+
+:class:`~forgeimporters.base.ToolImporter`
+------------------------------------------
+
+.. autoclass:: forgeimporters.base.ToolImporter
+   :members:
+
+:class:`~forgeimporters.base.ToolsValidator`
+--------------------------------------------
+
+.. autoclass:: forgeimporters.base.ToolsValidator
+   :members:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/227396fa/ForgeImporters/docs/importers/google.rst
----------------------------------------------------------------------
diff --git a/ForgeImporters/docs/importers/google.rst b/ForgeImporters/docs/importers/google.rst
new file mode 100644
index 0000000..5ed7306
--- /dev/null
+++ b/ForgeImporters/docs/importers/google.rst
@@ -0,0 +1,30 @@
+..     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.
+
+Google Code
+===========
+
+This importer imports projects and tools from Google Code.
+
+:mod:`forgeimporters.google`
+----------------------------
+
+.. autoclass:: forgeimporters.google.project.GoogleCodeProjectImporter
+   :members:
+
+.. autoclass:: forgeimporters.google.tracker.GoogleCodeTrackerImporter
+   :members:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/227396fa/ForgeImporters/docs/index.rst
----------------------------------------------------------------------
diff --git a/ForgeImporters/docs/index.rst b/ForgeImporters/docs/index.rst
new file mode 100644
index 0000000..37cfdcb
--- /dev/null
+++ b/ForgeImporters/docs/index.rst
@@ -0,0 +1,50 @@
+..     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.
+
+The *ForgeImporters* Package
+============================
+
+This package contains the base framework for project and
+tool importers, as well as the core importers, for the
+Allura platform.
+
+Project importers will be available at
+:file:`/{nbhd-prefix}/import_project/{importer-name}/`,
+while individual tool importers will be available under the
+Import sidebar entry on the project admin page.
+
+Available Importers
+===================
+
+The following importers are available in this package for
+use with an Allura system.
+
+.. toctree::
+   :maxdepth: 1
+   :glob:
+
+   importers/*
+
+Importer Framework
+==================
+
+The following classes make up the base framework for
+importers.
+
+.. toctree::
+
+   framework

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/227396fa/ForgeImporters/docs/make.bat
----------------------------------------------------------------------
diff --git a/ForgeImporters/docs/make.bat b/ForgeImporters/docs/make.bat
new file mode 100644
index 0000000..611d010
--- /dev/null
+++ b/ForgeImporters/docs/make.bat
@@ -0,0 +1,130 @@
+@ECHO OFF
+
+REM    Licensed to the Apache Software Foundation (ASF) under one
+REM    or more contributor license agreements.  See the NOTICE file
+REM    distributed with this work for additional information
+REM    regarding copyright ownership.  The ASF licenses this file
+REM    to you under the Apache License, Version 2.0 (the
+REM    "License"); you may not use this file except in compliance
+REM    with the License.  You may obtain a copy of the License at
+
+REM      http://www.apache.org/licenses/LICENSE-2.0
+
+REM    Unless required by applicable law or agreed to in writing,
+REM    software distributed under the License is distributed on an
+REM    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+REM    KIND, either express or implied.  See the License for the
+REM    specific language governing permissions and limitations
+REM    under the License.
+
+REM Command file for Sphinx documentation
+
+set SPHINXBUILD=sphinx-build
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+	:help
+	echo.Please use `make ^<target^>` where ^<target^> is one of
+	echo.  html      to make standalone HTML files
+	echo.  dirhtml   to make HTML files named index.html in directories
+	echo.  pickle    to make pickle files
+	echo.  json      to make JSON files
+	echo.  htmlhelp  to make HTML files and a HTML help project
+	echo.  qthelp    to make HTML files and a qthelp project
+	echo.  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+	echo.  changes   to make an overview over all changed/added/deprecated items
+	echo.  linkcheck to check all external links for integrity
+	echo.  doctest   to run all doctests embedded in the documentation if enabled
+	goto end
+)
+
+if "%1" == "clean" (
+	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+	del /q /s %BUILDDIR%\*
+	goto end
+)
+
+if "%1" == "html" (
+	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+	goto end
+)
+
+if "%1" == "dirhtml" (
+	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+	goto end
+)
+
+if "%1" == "pickle" (
+	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+	echo.
+	echo.Build finished; now you can process the pickle files.
+	goto end
+)
+
+if "%1" == "json" (
+	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+	echo.
+	echo.Build finished; now you can process the JSON files.
+	goto end
+)
+
+if "%1" == "htmlhelp" (
+	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+	echo.
+	echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+	goto end
+)
+
+if "%1" == "qthelp" (
+	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+	echo.
+	echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\allura.qhcp
+	echo.To view the help file:
+	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\allura.ghc
+	goto end
+)
+
+if "%1" == "latex" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	echo.
+	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "changes" (
+	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+	echo.
+	echo.The overview file is in %BUILDDIR%/changes.
+	goto end
+)
+
+if "%1" == "linkcheck" (
+	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+	echo.
+	echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+	goto end
+)
+
+if "%1" == "doctest" (
+	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+	echo.
+	echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+	goto end
+)
+
+:end

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/227396fa/ForgeImporters/forgeimporters/base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index bf22f8a..034a580 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -27,11 +27,19 @@ from allura.controllers import BaseController
 
 class ProjectImporter(BaseController):
     """
+    Base class for project importers.
+
+    Subclases are required to implement the :meth:`index()` and
+    :meth:`process()` views described below.
     """
     source = None
 
     @LazyProperty
     def tool_importers(self):
+        """
+        List of all tool importers that import from the same source
+        as this project importer.
+        """
         tools = {}
         for ep in iter_entry_points('allura.importers'):
             epv = ep.load()
@@ -43,7 +51,7 @@ class ProjectImporter(BaseController):
         """
         Override and expose this view to present the project import form.
 
-        The template used by this view should extend the base template in:
+        The template used by this view should extend the base template in::
 
             jinja:forgeimporters:templates/project_base.html
 
@@ -65,6 +73,29 @@ class ProjectImporter(BaseController):
 
 class ToolImporter(object):
     """
+    Base class for tool importers.
+
+    Subclasses are required to implement :meth:`import_tool()` described
+    below and define the following attributes:
+
+    .. py:attribute:: target_app
+
+       A reference or list of references to the tool(s) that this imports
+       to.  E.g.::
+
+            target_app = [forgegit.ForgeGitApp, forgehg.ForgeHgApp]
+
+    .. py:attribute:: source
+
+       A string indicating where this imports from.  This must match the
+       `source` value of the :class:`ProjectImporter` for this importer to
+       be discovered during full-project imports.  E.g.::
+
+            source = 'Google Code'
+
+    .. py:attribute:: controller
+
+       The controller for this importer, to handle single tool imports.
     """
     target_app = None  # app or list of apps
     source = None  # string description of source, must match project importer
@@ -72,11 +103,17 @@ class ToolImporter(object):
 
     @classmethod
     def by_name(self, name):
+        """
+        Return a ToolImporter subclass instance given its entry-point name.
+        """
         for ep in iter_entry_points('allura.importers', name):
             return ep.load()()
 
     @classmethod
     def by_app(self, app):
+        """
+        Return a ToolImporter subclass instance given its target_app class.
+        """
         importers = {}
         for ep in iter_entry_points('allura.importers'):
             importer = ep.load()
@@ -84,18 +121,31 @@ class ToolImporter(object):
                 importers[ep.name] = importer()
         return importers
 
-    def import_tool(self, project=None, mount_point=None):
+    def import_tool(self, project, project_name, mount_point=None, mount_label=None):
         """
         Override this method to perform the tool import.
+
+        :param project: the Allura project to import to
+        :param project_name: the name of the remote project to import from
+        :param mount_point: the mount point name, to override the default
+        :param mount_label: the mount label name, to override the default
         """
         raise NotImplementedError
 
     @property
     def tool_label(self):
+        """
+        The label for this tool importer.  Defaults to the `tool_label` from
+        the `target_app`.
+        """
         return getattr(aslist(self.target_app)[0], 'tool_label', None)
 
     @property
     def tool_description(self):
+        """
+        The description for this tool importer.  Defaults to the `tool_description`
+        from the `target_app`.
+        """
         return getattr(aslist(self.target_app)[0], 'tool_description', None)
 
     def tool_icon(self, theme, size):
@@ -103,6 +153,12 @@ class ToolImporter(object):
 
 
 class ToolsValidator(fev.Set):
+    """
+    Validates the list of tool importers during a project import.
+
+    This verifies that the tools selected are available and valid
+    for this source.
+    """
     def __init__(self, source, *a, **kw):
         super(ToolsValidator, self).__init__(*a, **kw)
         self.source = source

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/227396fa/ForgeImporters/forgeimporters/google/project.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/project.py b/ForgeImporters/forgeimporters/google/project.py
index 32b1976..d579606 100644
--- a/ForgeImporters/forgeimporters/google/project.py
+++ b/ForgeImporters/forgeimporters/google/project.py
@@ -47,6 +47,13 @@ class GoogleCodeProjectForm(schema.Schema):
 
 
 class GoogleCodeProjectImporter(base.ProjectImporter):
+    """
+    Project importer for Google Code.
+
+    This imports project metadata, including summary, icon, and license,
+    as well as providing the UI for importing individual tools during project
+    import.
+    """
     source = 'Google Code'
 
     def __init__(self, neighborhood, *a, **kw):


[24/50] git commit: [#3154] ticket:391 ForgeBlog REST API

Posted by jo...@apache.org.
[#3154] ticket:391 ForgeBlog REST API


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

Branch: refs/heads/cj/6422
Commit: af5c8e683f96007b64108a0e89846c949e96ad3e
Parents: 0065ba1
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Wed Jul 10 14:57:48 2013 +0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Jul 31 21:00:24 2013 +0000

----------------------------------------------------------------------
 ForgeBlog/forgeblog/main.py                     |  87 +++++++++-
 ForgeBlog/forgeblog/model/blog.py               |   9 +
 .../forgeblog/tests/functional/test_rest.py     | 169 +++++++++++++++++++
 3 files changed, 264 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/af5c8e68/ForgeBlog/forgeblog/main.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/main.py b/ForgeBlog/forgeblog/main.py
index 5f54a38..0f4e7e6 100644
--- a/ForgeBlog/forgeblog/main.py
+++ b/ForgeBlog/forgeblog/main.py
@@ -30,6 +30,7 @@ from paste.deploy.converters import asbool
 import formencode
 from formencode import validators
 from webob import exc
+from urllib import unquote
 
 from ming.orm import session
 
@@ -45,7 +46,7 @@ from allura.lib.widgets.subscriptions import SubscribeForm
 from allura.lib.widgets import form_fields as ffw
 from allura.lib.widgets.search import SearchResults, SearchHelp
 from allura import model as M
-from allura.controllers import BaseController, AppDiscussionController
+from allura.controllers import BaseController, AppDiscussionController, AppDiscussionRestController
 from allura.controllers.feed import FeedArgs, FeedController
 
 # Local imports
@@ -101,6 +102,7 @@ class ForgeBlogApp(Application):
         Application.__init__(self, project, config)
         self.root = RootController()
         self.admin = BlogAdminController(self)
+        self.api_root = RootRestController()
 
     @Property
     def external_feeds_list():
@@ -439,3 +441,86 @@ class BlogAdminController(DefaultAdminController):
             flash('Invalid link(s): %s' % ','.join(link for link in invalid_list), 'error')
 
         redirect(c.project.url()+'admin/tools')
+
+
+class RootRestController(BaseController):
+    def __init__(self):
+        self._discuss = AppDiscussionRestController()
+
+    def _check_security(self):
+        require_access(c.app, 'read')
+
+    @expose('json:')
+    def index(self, title='', text='', state='draft', labels='', **kw):
+        if request.method == 'POST':
+            require_access(c.app, 'write')
+            post = BM.BlogPost()
+            post.title = title
+            post.state = state
+            post.text = text
+            post.labels = labels.split(',')
+            post.neighborhood_id = c.project.neighborhood_id
+            post.make_slug()
+            M.Thread.new(discussion_id=post.app_config.discussion_id,
+                         ref_id=post.index_id(),
+                         subject='%s discussion' % post.title)
+
+            post.viewable_by = ['all']
+            post.commit()
+            return post.__json__()
+        else:
+            post_titles = []
+            query_filter = dict(app_config_id=c.app.config._id, deleted=False)
+            if not has_access(c.app, 'write')():
+                query_filter['state'] = 'published'
+            posts = BM.BlogPost.query.find(query_filter)
+            for post in posts:
+                if has_access(post, 'read')():
+                    post_titles.append({'title': post.title, 'url': h.absurl('/rest' + post.url())})
+            return dict(posts=post_titles)
+
+    @expose()
+    def _lookup(self, year=None, month=None, title=None, *rest):
+        if not (year and month and title):
+            raise exc.HTTPNotFound()
+        slug = '/'.join((year, month, urllib2.unquote(title).decode('utf-8')))
+        post = BM.BlogPost.query.get(slug=slug, app_config_id=c.app.config._id)
+        if not post:
+            raise exc.HTTPNotFound()
+        return PostRestController(post), rest
+
+
+class PostRestController(BaseController):
+
+    def __init__(self, post):
+        self.post = post
+
+    def _check_security(self):
+        if self.post:
+            require_access(self.post, 'read')
+
+    @h.vardec
+    @expose('json:')
+    def index(self, **kw):
+        if request.method == 'POST':
+            return self._update_post(**kw)
+        else:
+            if self.post.state == 'draft':
+                require_access(self.post, 'write')
+            return self.post.__json__()
+
+    def _update_post(self, **post_data):
+        require_access(self.post, 'write')
+        if 'delete' in post_data:
+            self.post.delete()
+            return {}
+        if 'title' in post_data:
+            self.post.title = post_data['title']
+        if 'text' in post_data:
+            self.post.text = post_data['text']
+        if 'state' in post_data:
+            self.post.state = post_data['state']
+        if 'labels' in post_data:
+            self.post.labels = post_data['labels'].split(',')
+        self.post.commit()
+        return self.post.__json__()
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/af5c8e68/ForgeBlog/forgeblog/model/blog.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/model/blog.py b/ForgeBlog/forgeblog/model/blog.py
index b43cb8e..07983a0 100644
--- a/ForgeBlog/forgeblog/model/blog.py
+++ b/ForgeBlog/forgeblog/model/blog.py
@@ -256,6 +256,15 @@ class BlogPost(M.VersionedArtifact, ActivityObject):
             M.Notification.post(
                 artifact=self, topic='metadata', text=description, subject=subject)
 
+    def __json__(self):
+        return dict(super(BlogPost, self).__json__(),
+                    title=self.title,
+                    url=h.absurl('/rest' + self.url()),
+                    text=self.text,
+                    labels=self.labels,
+                    state=self.state)
+
+
 class Attachment(M.BaseAttachment):
     ArtifactClass=BlogPost
     class __mongometa__:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/af5c8e68/ForgeBlog/forgeblog/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/tests/functional/test_rest.py b/ForgeBlog/forgeblog/tests/functional/test_rest.py
new file mode 100644
index 0000000..5addcde
--- /dev/null
+++ b/ForgeBlog/forgeblog/tests/functional/test_rest.py
@@ -0,0 +1,169 @@
+# coding: utf-8
+
+#       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 nose.tools import assert_equal
+from allura.lib import helpers as h
+from allura.tests import decorators as td
+from allura import model as M
+from alluratest.controller import TestRestApiBase
+from forgeblog import model as BM
+
+
+class TestBlogApi(TestRestApiBase):
+
+    def setUp(self):
+        super(TestBlogApi, self).setUp()
+        self.setup_with_tools()
+
+    @td.with_tool('test', 'Blog', 'blog')
+    def setup_with_tools(self):
+        h.set_context('test', 'blog', neighborhood='Projects')
+
+    def test_create_post(self):
+        data = {
+            'title': 'test',
+            'text': 'test text',
+            'state': 'published',
+            'labels': 'label1, label2'
+        }
+        r = self.api_post('/rest/p/test/blog/', **data)
+        assert_equal(r.status_int, 200)
+        url = '/rest' + BM.BlogPost.query.find().first().url()
+        r = self.api_get('/rest/p/test/blog/')
+        assert_equal(r.json['posts'][0]['title'], 'test')
+        assert_equal(r.json['posts'][0]['url'], h.absurl(url))
+
+        r = self.api_get(url)
+        assert_equal(r.json['title'], 'test')
+        assert_equal(r.json['text'], data['text'])
+        assert_equal(r.json['state'], data['state'])
+        assert_equal(r.json['labels'], data['labels'].split(','))
+
+    def test_update_post(self):
+        data = {
+            'title': 'test',
+            'text': 'test text',
+            'state': 'published',
+            'labels': 'label1, label2'
+        }
+        r = self.api_post('/rest/p/test/blog/', **data)
+        assert_equal(r.status_int, 200)
+        url = '/rest' + BM.BlogPost.query.find().first().url()
+        data = {
+            'text': 'test text2',
+            'state': 'draft',
+            'labels': 'label3'
+        }
+        self.api_post(url, **data)
+        r = self.api_get(url)
+        assert_equal(r.json['title'], 'test')
+        assert_equal(r.json['text'], data['text'])
+        assert_equal(r.json['state'], data['state'])
+        assert_equal(r.json['labels'], data['labels'].split(','))
+
+    def test_delete_post(self):
+        data = {
+            'title': 'test',
+            'state': 'published',
+            'labels': 'label1, label2'
+        }
+        r = self.api_post('/rest/p/test/blog/', **data)
+        assert_equal(r.status_int, 200)
+        url = '/rest' + BM.BlogPost.query.find().first().url()
+        self.api_post(url, delete='')
+        r = self.api_get(url)
+        assert_equal(r.status_int, 404)
+
+    def test_post_does_not_exist(self):
+        r = self.api_get('/rest/p/test/blog/2013/07/fake/')
+        assert_equal(r.status_int, 404)
+
+    def test_read_permissons(self):
+        self.api_post('/rest/p/test/blog/', title='test', text='test text', state='published')
+        self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'}, status=200)
+        p = M.Project.query.get(shortname='test')
+        acl = p.app_instance('blog').config.acl
+        anon = M.ProjectRole.by_name('*anonymous')._id
+        anon_read = M.ACE.allow(anon, 'read')
+        acl.remove(anon_read)
+        self.app.get('/rest/p/test/blog/',
+                     extra_environ={'username': '*anonymous'},
+                     status=401)
+
+    def test_new_post_permissons(self):
+        self.app.post('/rest/p/test/blog/',
+                      params=dict(title='test', text='test text', state='published'),
+                      extra_environ={'username': '*anonymous'},
+                      status=401)
+        p = M.Project.query.get(shortname='test')
+        acl = p.app_instance('blog').config.acl
+        anon = M.ProjectRole.by_name('*anonymous')._id
+        anon_write = M.ACE.allow(anon, 'write')
+        acl.append(anon_write)
+        self.app.post('/rest/p/test/blog/',
+                      params=dict(title='test', text='test text', state='published'),
+                      extra_environ={'username': '*anonymous'},
+                      status=200)
+
+    def test_update_post_permissons(self):
+        self.api_post('/rest/p/test/blog/', title='test', text='test text', state='published')
+        url = '/rest' + BM.BlogPost.query.find().first().url()
+        self.app.post(url.encode('utf-8'),
+                      params=dict(title='test2', text='test text2', state='published'),
+                      extra_environ={'username': '*anonymous'},
+                      status=401)
+        p = M.Project.query.get(shortname='test')
+        acl = p.app_instance('blog').config.acl
+        anon = M.ProjectRole.by_name('*anonymous')._id
+        anon_write = M.ACE.allow(anon, 'write')
+        acl.append(anon_write)
+        self.app.post(url.encode('utf-8'),
+                      params=dict(title='test2', text='test text2', state='published'),
+                      extra_environ={'username': '*anonymous'},
+                      status=200)
+        r = self.api_get(url)
+        assert_equal(r.json['title'], 'test2')
+        assert_equal(r.json['text'], 'test text2')
+        assert_equal(r.json['state'], 'published')
+
+    def test_permission_draft_post(self):
+        self.api_post('/rest/p/test/blog/', title='test', text='test text', state='draft')
+        r = self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'})
+        assert_equal(r.json, {'posts': []})
+        url = '/rest' + BM.BlogPost.query.find().first().url()
+        self.app.post(url.encode('utf-8'),
+                      params=dict(title='test2', text='test text2', state='published'),
+                      extra_environ={'username': '*anonymous'},
+                      status=401)
+        p = M.Project.query.get(shortname='test')
+        acl = p.app_instance('blog').config.acl
+        anon = M.ProjectRole.by_name('*anonymous')._id
+        anon_write = M.ACE.allow(anon, 'write')
+        acl.append(anon_write)
+        r = self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'})
+        assert_equal(r.json['posts'][0]['title'], 'test')
+
+    def test_draft_post(self):
+        self.api_post('/rest/p/test/blog/', title='test', text='test text', state='draft')
+        r = self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'})
+        assert_equal(r.json, {'posts': []})
+        url = '/rest' + BM.BlogPost.query.find().first().url()
+        self.api_post(url, state='published')
+        r = self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'})
+        assert_equal(r.json['posts'][0]['title'], 'test')


[45/50] git commit: [#4665] ticket:401 added test for MergeRequest help text

Posted by jo...@apache.org.
[#4665] ticket:401 added test for MergeRequest help text


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

Branch: refs/heads/cj/6422
Commit: 37b8fff82e4b555ce632ff3aeca8df19b4f09a34
Parents: 439222c
Author: Anton Kasyanov <mi...@gmail.com>
Authored: Wed Jul 31 14:00:31 2013 +0300
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 7 22:35:27 2013 +0000

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


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/37b8fff8/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 6eafcb6..d8c3f2c 100644
--- a/ForgeGit/forgegit/tests/functional/test_controllers.py
+++ b/ForgeGit/forgegit/tests/functional/test_controllers.py
@@ -501,6 +501,10 @@ class TestFork(_TestCase):
         assert_equal(rev_links[0].getText(), '[%s]' % c_id[:6])
         assert_equal(browse_links[0].get('href'), '/p/test2/code/ci/%s/tree' % c_id)
         assert_equal(browse_links[0].getText(), 'Tree')
+        merge_instructions = r.html.findAll('textarea')[0].getText()
+        assert 'git checkout master' in merge_instructions
+        assert 'git fetch git://git.localhost/p/test2/code master' in merge_instructions
+        assert 'git merge {}'.format(c_id) in merge_instructions
 
     def test_merge_request_list_view(self):
         r, mr_num = self._request_merge()


[38/50] git commit: [#6480] Initialize classes to prevent exc if no href

Posted by jo...@apache.org.
[#6480] Initialize classes to prevent exc if no href

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/f398a06e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/f398a06e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/f398a06e

Branch: refs/heads/cj/6422
Commit: f398a06e1ca979922c366d30ad17060b53eb5988
Parents: 7553977
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 19:23:16 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/markdown_extensions.py | 1 +
 1 file changed, 1 insertion(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/f398a06e/Allura/allura/lib/markdown_extensions.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/markdown_extensions.py b/Allura/allura/lib/markdown_extensions.py
index 4879bf4..6e1004d 100644
--- a/Allura/allura/lib/markdown_extensions.py
+++ b/Allura/allura/lib/markdown_extensions.py
@@ -99,6 +99,7 @@ class ForgeLinkPattern(markdown.inlinepatterns.LinkPattern):
         except IndexError:
             title = None
 
+        classes = ''
         if href:
             if href == 'TOC':
                 return '[TOC]'  # skip TOC


[40/50] git commit: [#6480] move html2text import inline, so file can be imported even if not installed

Posted by jo...@apache.org.
[#6480] move html2text import inline, so file can be imported even if not installed


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

Branch: refs/heads/cj/6422
Commit: 63e09352faba33a3935e155c5f6bf29d1c28989d
Parents: 04cd1ed
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Wed Aug 7 17:50:35 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 17:50:35 2013 +0000

----------------------------------------------------------------------
 Allura/allura/scripts/trac_export.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63e09352/Allura/allura/scripts/trac_export.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/trac_export.py b/Allura/allura/scripts/trac_export.py
index bce548e..e45175b 100644
--- a/Allura/allura/scripts/trac_export.py
+++ b/Allura/allura/scripts/trac_export.py
@@ -30,7 +30,6 @@ from itertools import islice
 from datetime import datetime
 
 import feedparser
-from html2text import html2text
 from BeautifulSoup import BeautifulSoup, NavigableString
 import dateutil.parser
 import pytz
@@ -141,6 +140,7 @@ class TracExport(object):
 
     def parse_ticket_comments(self, id):
         # Use RSS export to get ticket comments
+        from html2text import html2text
         url = self.full_url(self.TICKET_URL % id, 'rss')
         self.log_url(url)
         d = feedparser.parse(url)


[10/50] git commit: [#6446] ticket:400 fixed test for showing only ok posts

Posted by jo...@apache.org.
[#6446] ticket:400 fixed test for showing only ok posts


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

Branch: refs/heads/cj/6422
Commit: 24cd410cfe4d9a89541faa1ed4c6204b394cc081
Parents: 58a2727
Author: Anton Kasyanov <mi...@gmail.com>
Authored: Mon Jul 22 16:31:34 2013 +0300
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 14:55:48 2013 +0000

----------------------------------------------------------------------
 .../forgediscussion/tests/functional/test_rest.py           | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/24cd410c/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
index 3aca6e0..6c45a2a 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
@@ -24,6 +24,7 @@ from allura.tests import decorators as td
 from allura import model as M
 from alluratest.controller import TestRestApiBase
 from forgediscussion.model import ForumThread
+from ming.orm import ThreadLocalORMSession
 
 
 class TestDiscussionApiBase(TestRestApiBase):
@@ -186,9 +187,6 @@ class TestRootRestController(TestDiscussionApiBase):
         assert_equal(resp.json['limit'], 1)
 
     def test_topic_show_ok_only(self):
-        # import logging
-        # log = logging.getLogger(__name__)
-
         thread = ForumThread.query.find({'subject': 'Hi guys'}).first()        
         url = '/rest/p/test/discussion/general/thread/%s/' % thread._id
         resp = self.app.get(url)
@@ -198,12 +196,9 @@ class TestRootRestController(TestDiscussionApiBase):
         last_post = thread.last_post
         last_post.status = 'pending'
         last_post.commit()
-
+        ThreadLocalORMSession.flush_all()
         resp = self.app.get(url)
         posts = resp.json['topic']['posts']        
-
-        # log.info('ready to debug')
-        # log.info(posts)
         assert_equal(len(posts), 1)
 
     def test_security(self):


[11/50] git commit: [#6446] ticket:400 in forum api return only ok threads

Posted by jo...@apache.org.
[#6446] ticket:400 in forum api return only ok threads


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

Branch: refs/heads/cj/6422
Commit: 58a2727bd9cf4af9644ecbc2188d7f5b88c52871
Parents: dbb1cb1
Author: Anton Kasyanov <mi...@gmail.com>
Authored: Mon Jul 22 16:17:11 2013 +0300
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 14:55:48 2013 +0000

----------------------------------------------------------------------
 .../forgediscussion/controllers/root.py            |  2 +-
 .../forgediscussion/tests/functional/test_rest.py  | 17 +++++++++++++++++
 2 files changed, 18 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/58a2727b/ForgeDiscussion/forgediscussion/controllers/root.py
----------------------------------------------------------------------
diff --git a/ForgeDiscussion/forgediscussion/controllers/root.py b/ForgeDiscussion/forgediscussion/controllers/root.py
index 7dd18c7..f4354cc 100644
--- a/ForgeDiscussion/forgediscussion/controllers/root.py
+++ b/ForgeDiscussion/forgediscussion/controllers/root.py
@@ -305,7 +305,7 @@ class ForumRestController(BaseController):
                                         num_views=t.num_views,
                                         url=h.absurl('/rest' + t.url()),
                                         last_post=t.last_post)
-                                   for t in topics]
+                                   for t in topics if t.status == 'ok']
         json['count'] = count
         json['page'] = page
         json['limit'] = limit

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/58a2727b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
index 7fc3499..3aca6e0 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
@@ -104,6 +104,23 @@ class TestRootRestController(TestDiscussionApiBase):
         url = 'http://localhost:80/rest/p/test/discussion/general/thread/%s/' % t._id
         assert_equal(topics[1]['url'], url)
 
+    def test_forum_show_ok_topics(self):
+        forum = self.api_get('/rest/p/test/discussion/general/')
+        forum = forum.json['forum']
+        assert_equal(forum['name'], 'General Discussion')
+        topics = forum['topics']
+        assert_equal(len(topics), 2)
+        self.create_topic('general', 'Hi again', 'It should not be shown')
+        t = ForumThread.query.find({'subject': 'Hi again'}).first()
+        first_post = t.first_post
+        first_post.status = u'pending'
+        first_post.commit()
+        forum = self.api_get('/rest/p/test/discussion/general/')
+        forum = forum.json['forum']
+        assert_equal(forum['name'], 'General Discussion')
+        topics = forum['topics']
+        assert_equal(len(topics), 2)
+
     def test_topic(self):
         forum = self.api_get('/rest/p/test/discussion/general/')
         forum = forum.json['forum']


[32/50] git commit: [#6480] TracExport bug fixes

Posted by jo...@apache.org.
[#6480] TracExport bug fixes

- Pass in options explicitly instead of attempting to read from a
  non-existent global object.
- Improve logging.
- Fix infinite loop bug.

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/35cc655a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/35cc655a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/35cc655a

Branch: refs/heads/cj/6422
Commit: 35cc655a2913532a6471488d82a6452365e08e4d
Parents: 280aff0
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 18:50:46 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:23 2013 +0000

----------------------------------------------------------------------
 Allura/allura/scripts/trac_export.py | 25 +++++++++++++++++--------
 1 file changed, 17 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/35cc655a/Allura/allura/scripts/trac_export.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/trac_export.py b/Allura/allura/scripts/trac_export.py
index aeb14ea..bce548e 100644
--- a/Allura/allura/scripts/trac_export.py
+++ b/Allura/allura/scripts/trac_export.py
@@ -17,7 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-
+import logging
 import sys
 import csv
 import urlparse
@@ -35,6 +35,8 @@ from BeautifulSoup import BeautifulSoup, NavigableString
 import dateutil.parser
 import pytz
 
+log = logging.getLogger(__name__)
+
 
 def parse_options():
     optparser = OptionParser(usage=''' %prog <Trac URL>
@@ -66,7 +68,7 @@ class TracExport(object):
         'owner': 'assigned_to',
     }
 
-    def __init__(self, base_url, start_id=1):
+    def __init__(self, base_url, start_id=1, verbose=False, do_attachments=True):
         """start_id - start with at least that ticket number (actual returned
                       ticket may have higher id if we don't have access to exact
                       one).
@@ -78,6 +80,9 @@ class TracExport(object):
         self.ticket_map = {}
         self.start_id = start_id
         self.page = (start_id - 1) / self.PAGE_SIZE + 1
+        self.verbose = verbose
+        self.do_attachments = do_attachments
+        self.exhausted = False
         self.ticket_queue = self.next_ticket_ids()
 
     def remap_fields(self, dict):
@@ -98,9 +103,9 @@ class TracExport(object):
         glue = '&' if '?' in suburl else '?'
         return  url + glue + 'format=' + type
 
-    @staticmethod
-    def log_url(url):
-        if options.verbose:
+    def log_url(self, url):
+        log.info(url)
+        if self.verbose:
             print >>sys.stderr, url
 
     @classmethod
@@ -198,7 +203,7 @@ class TracExport(object):
         '''
         t = self.parse_ticket_body(id)
         t['comments'] = self.parse_ticket_comments(id)
-        if options.do_attachments:
+        if self.do_attachments:
             atts = self.parse_ticket_attachments(id)
             if atts:
                 t['attachments'] = atts
@@ -230,6 +235,9 @@ class TracExport(object):
                 res.append((id, extra))
         self.page += 1
 
+        if len(res) < self.PAGE_SIZE:
+            self.exhausted = True
+
         return res
 
     def __iter__(self):
@@ -238,7 +246,7 @@ class TracExport(object):
     def next(self):
         while True:
             # queue empty, try to fetch more
-            if len(self.ticket_queue) == 0:
+            if len(self.ticket_queue) == 0 and not self.exhausted:
                 self.ticket_queue = self.next_ticket_ids()
             # there aren't any more, we're really done
             if len(self.ticket_queue) == 0:
@@ -258,7 +266,8 @@ class DateJSONEncoder(json.JSONEncoder):
 
 def main():
     options, args = parse_options()
-    ex = TracExport(args[0], start_id=options.start_id)
+    ex = TracExport(args[0], start_id=options.start_id,
+            verbose=options.verbose, do_attachments=options.do_attachments)
     # Implement iterator sequence limiting using islice()
     doc = [t for t in islice(ex, options.limit)]
 


[13/50] git commit: [#6446] ticket:400 removed threads from forum json

Posted by jo...@apache.org.
[#6446] ticket:400 removed threads from forum json


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

Branch: refs/heads/cj/6422
Commit: 8b994ee25a10fe4f87cc898c506591c7b51fc71c
Parents: 6436aa5
Author: Anton Kasyanov <mi...@gmail.com>
Authored: Wed Jul 24 17:47:55 2013 +0300
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 14:55:49 2013 +0000

----------------------------------------------------------------------
 ForgeDiscussion/forgediscussion/controllers/root.py | 2 ++
 1 file changed, 2 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8b994ee2/ForgeDiscussion/forgediscussion/controllers/root.py
----------------------------------------------------------------------
diff --git a/ForgeDiscussion/forgediscussion/controllers/root.py b/ForgeDiscussion/forgediscussion/controllers/root.py
index f4354cc..fa0e97b 100644
--- a/ForgeDiscussion/forgediscussion/controllers/root.py
+++ b/ForgeDiscussion/forgediscussion/controllers/root.py
@@ -299,6 +299,8 @@ class ForumRestController(BaseController):
         count = topics.count()
         json = {}
         json['forum'] = self.forum.__json__()
+        # it appears that topics replace threads here
+        del json['forum']['threads']
         json['forum']['topics'] = [dict(_id=t._id,
                                         subject=t.subject,
                                         num_replies=t.num_replies,


[02/50] git commit: [#6139] ticket:399 Fix imports for ScriptTask

Posted by jo...@apache.org.
[#6139] ticket:399 Fix imports for ScriptTask


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

Branch: refs/heads/cj/6422
Commit: 24d39a7d0703f9590e556edac9f5d083cb9240e7
Parents: fcf24fc
Author: Igor Bondarenko <je...@gmail.com>
Authored: Fri Jul 26 08:05:27 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Tue Jul 30 19:29:23 2013 +0000

----------------------------------------------------------------------
 ForgeWiki/forgewiki/scripts/wiki_from_trac/wiki_from_trac.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/24d39a7d/ForgeWiki/forgewiki/scripts/wiki_from_trac/wiki_from_trac.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/scripts/wiki_from_trac/wiki_from_trac.py b/ForgeWiki/forgewiki/scripts/wiki_from_trac/wiki_from_trac.py
index b08df4f..65630aa 100644
--- a/ForgeWiki/forgewiki/scripts/wiki_from_trac/wiki_from_trac.py
+++ b/ForgeWiki/forgewiki/scripts/wiki_from_trac/wiki_from_trac.py
@@ -20,8 +20,8 @@ import logging
 from tempfile import NamedTemporaryFile
 from tg.decorators import cached_property
 
-from extractors import WikiExporter
-from loaders import load_data
+from forgewiki.scripts.wiki_from_trac.extractors import WikiExporter
+from forgewiki.scripts.wiki_from_trac.loaders import load_data
 
 from allura.scripts import ScriptTask
 


[25/50] git commit: [#3154] ticket:407 added pagination and location header tests for ForgeBlog API

Posted by jo...@apache.org.
[#3154]  ticket:407 added pagination and location header tests for ForgeBlog API


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

Branch: refs/heads/cj/6422
Commit: 93d12e6989d08658dad218d7bab0ec51fb675192
Parents: e23020d
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Mon Jul 29 16:03:35 2013 +0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Jul 31 21:00:25 2013 +0000

----------------------------------------------------------------------
 ForgeBlog/forgeblog/tests/functional/test_rest.py | 6 ++++++
 1 file changed, 6 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/93d12e69/ForgeBlog/forgeblog/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/tests/functional/test_rest.py b/ForgeBlog/forgeblog/tests/functional/test_rest.py
index 0ab05c2..f545c7e 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_rest.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_rest.py
@@ -16,6 +16,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from datetime import date
 
 from nose.tools import assert_equal
 from allura.lib import helpers as h
@@ -43,6 +44,7 @@ class TestBlogApi(TestRestApiBase):
             'labels': 'label1, label2'
         }
         r = self.api_post('/rest/p/test/blog/', **data)
+        assert_equal(r.location, 'http://localhost:80/rest/p/test/blog/%s/%s/test/' % (date.today().strftime("%Y"), date.today().strftime("%m")))
         assert_equal(r.status_int, 201)
         url = '/rest' + BM.BlogPost.query.find().first().url()
         r = self.api_get('/rest/p/test/blog/')
@@ -56,6 +58,8 @@ class TestBlogApi(TestRestApiBase):
         assert_equal(r.json['state'], data['state'])
         assert_equal(r.json['labels'], data['labels'].split(','))
 
+
+
     def test_update_post(self):
         data = {
             'title': 'test',
@@ -175,12 +179,14 @@ class TestBlogApi(TestRestApiBase):
         self.api_post('/rest/p/test/blog/', title='test3', text='test text3', state='published')
         r = self.api_get('/rest/p/test/blog/', limit='1', page='0')
         assert_equal(r.json['posts'][0]['title'], 'test3')
+        assert_equal(len(r.json['posts']), 1)
         assert_equal(r.json['count'], 3)
         assert_equal(r.json['limit'], 1)
         assert_equal(r.json['page'], 0)
         r = self.api_get('/rest/p/test/blog/', limit='2', page='0')
         assert_equal(r.json['posts'][0]['title'], 'test3')
         assert_equal(r.json['posts'][1]['title'], 'test2')
+        assert_equal(len(r.json['posts']), 2)
         assert_equal(r.json['count'], 3)
         assert_equal(r.json['limit'], 2)
         assert_equal(r.json['page'], 0)


[48/50] git commit: [#6458] standardize importer label

Posted by jo...@apache.org.
[#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/6422
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,


[35/50] git commit: [#6480] Refactor import_tool() params

Posted by jo...@apache.org.
[#6480] Refactor import_tool() params

- project and user are the only guaranteed params for every
  importer, and should therefore be the only positional args
- All other args to import_tool, even project_name (which refers
  to the name of the project being imported), should be passed
  by keyword.

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/85c29b92
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/85c29b92
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/85c29b92

Branch: refs/heads/cj/6422
Commit: 85c29b926350a1c6cb5f5178fcabc031a12c45ff
Parents: 2379488
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Wed Aug 7 13:32:14 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/base.py                     | 10 +++++-----
 ForgeImporters/forgeimporters/google/code.py              |  6 +++---
 ForgeImporters/forgeimporters/google/tests/test_code.py   |  4 +++-
 ForgeImporters/forgeimporters/google/tracker.py           |  3 ++-
 .../forgeimporters/tests/google/test_tracker.py           |  5 +++--
 ForgeImporters/forgeimporters/tests/test_base.py          |  5 +++--
 ForgeImporters/forgeimporters/trac/tests/test_tickets.py  |  5 ++---
 ForgeImporters/forgeimporters/trac/tests/test_wiki.py     |  5 ++---
 ForgeImporters/forgeimporters/trac/tickets.py             |  9 ++++-----
 ForgeImporters/forgeimporters/trac/wiki.py                |  9 ++++-----
 10 files changed, 31 insertions(+), 30 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index 7ad720c..3aea8c0 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -50,11 +50,10 @@ class ProjectImportForm(schema.Schema):
 
 
 @task
-def import_tool(importer_name, project_name, mount_point=None, mount_label=None, **kw):
+def import_tool(importer_name, project_name=None, mount_point=None, mount_label=None, **kw):
     importer = ToolImporter.by_name(importer_name)
-    importer.import_tool(project=c.project, user=c.user,
-            mount_point=mount_point,
-            mount_label=mount_label, **kw)
+    importer.import_tool(c.project, c.user, project_name=project_name,
+            mount_point=mount_point, mount_label=mount_label, **kw)
 
 
 class ProjectImporter(BaseController):
@@ -209,7 +208,8 @@ class ToolImporter(object):
                 importers[ep.name] = importer()
         return importers
 
-    def import_tool(self, project, project_name, mount_point=None, mount_label=None):
+    def import_tool(self, project, user, project_name=None,
+            mount_point=None, mount_label=None, **kw):
         """
         Override this method to perform the tool import.
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/google/code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/code.py b/ForgeImporters/forgeimporters/google/code.py
index 4457b55..c88d42a 100644
--- a/ForgeImporters/forgeimporters/google/code.py
+++ b/ForgeImporters/forgeimporters/google/code.py
@@ -86,7 +86,7 @@ class GoogleRepoImportController(BaseController):
     @require_post()
     @validate(GoogleRepoImportSchema(), error_handler=index)
     def create(self, gc_project_name, mount_point, mount_label, **kw):
-        app = GoogleRepoImporter.import_tool(c.project,
+        app = GoogleRepoImporter().import_tool(c.project, c.user,
                 project_name=gc_project_name,
                 mount_point=mount_point,
                 mount_label=mount_label)
@@ -100,8 +100,8 @@ class GoogleRepoImporter(ToolImporter):
     tool_label = 'Google Code Source Importer'
     tool_description = 'Import your SVN, Git, or Hg repo from Google Code'
 
-    def import_tool(self, project, project_name, mount_point=None, mount_label=None):
-    def import_tool(self, project, project_name, mount_point=None, mount_label=None, **kw):
+    def import_tool(self, project, user, project_name=None, mount_point=None,
+            mount_label=None, **kw):
         """ Import a Google Code repo into a new SVN, Git, or Hg Allura tool.
 
         """

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/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 fe6943b..13ec0c3 100644
--- a/ForgeImporters/forgeimporters/google/tests/test_code.py
+++ b/ForgeImporters/forgeimporters/google/tests/test_code.py
@@ -62,7 +62,8 @@ class TestGoogleRepoImporter(TestCase):
         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')
-        GoogleRepoImporter().import_tool(p, 'project_name')
+        GoogleRepoImporter().import_tool(p, Mock(name='c.user'),
+                project_name='project_name')
         get_repo_url.assert_called_once_with('project_name', 'git')
         p.install_app.assert_called_once_with('Git',
                 mount_point='code',
@@ -89,6 +90,7 @@ class TestGoogleRepoImportController(TestController, TestCase):
     @patch('forgeimporters.google.code.GoogleRepoImporter')
     def test_create(self, gri):
         from allura import model as M
+        gri = gri.return_value
         gri.import_tool.return_value = Mock()
         gri.import_tool.return_value.url.return_value = '/p/{}/mymount'.format(test_project_with_repo)
         params = dict(gc_project_name='poop',

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 4d37a16..95f53e4 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -42,7 +42,8 @@ class GoogleCodeTrackerImporter(ToolImporter):
             type='select',
         )
 
-    def import_tool(self, project, project_name, mount_point=None, mount_label=None):
+    def import_tool(self, project, user, project_name=None, 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']

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/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 d54ac90..e49f279 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -33,14 +33,15 @@ class TestTrackerImporter(TestCase):
         importer.process_labels = mock.Mock()
         importer.process_comments = mock.Mock()
         importer.postprocess_custom_fields = mock.Mock()
-        project = 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()]
         tickets = TM.Ticket.new.side_effect = [mock.Mock(), mock.Mock()]
         comments = extractor.iter_comments.side_effect = [mock.Mock(), mock.Mock()]
 
-        importer.import_tool(project, 'project_name', 'mount_point', 'mount_label')
+        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')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/tests/test_base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/test_base.py b/ForgeImporters/forgeimporters/tests/test_base.py
index 18b83ac..68be24d 100644
--- a/ForgeImporters/forgeimporters/tests/test_base.py
+++ b/ForgeImporters/forgeimporters/tests/test_base.py
@@ -28,10 +28,11 @@ from .. import base
 def test_import_tool(c, by_name):
     c.project = mock.Mock(name='project')
     c.user = mock.Mock(name='user')
-    base.import_tool('importer_name', 'project_name', 'mount_point', 'mount_label')
+    base.import_tool('importer_name', project_name='project_name',
+            mount_point='mount_point', mount_label='mount_label')
     by_name.assert_called_once_with('importer_name')
     by_name.return_value.import_tool.assert_called_once_with(c.project,
-            'project_name', user=c.user, mount_point='mount_point',
+            c.user, project_name='project_name', mount_point='mount_point',
             mount_label='mount_label')
 
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
index 2cce886..5f88eef 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -45,11 +45,10 @@ class TestTracTicketImporter(TestCase):
         project = Mock(name='Project', shortname='myproject')
         project.install_app.return_value = app
         user = Mock(name='User', _id='id')
-        res = importer.import_tool(project=project,
+        res = importer.import_tool(project, user,
                 mount_point='bugs',
                 mount_label='Bugs',
-                trac_url='http://example.com/trac/url',
-                user=user)
+                trac_url='http://example.com/trac/url')
         self.assertEqual(res, app)
         project.install_app.assert_called_once_with(
                 'Tickets', mount_point='bugs', mount_label='Bugs')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
index 71cc8b5..738e49b 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
@@ -50,11 +50,10 @@ class TestWikiTicketImporter(TestCase):
         project = Mock(name='Project', shortname='myproject')
         project.install_app.return_value = app
         user = Mock(name='User', _id='id')
-        res = importer.import_tool(project=project,
+        res = importer.import_tool(project, user,
                 mount_point='pages',
                 mount_label='Pages',
-                trac_url='http://example.com/trac/url',
-                user=user)
+                trac_url='http://example.com/trac/url')
         self.assertEqual(res, app)
         project.install_app.assert_called_once_with(
                 'Wiki', mount_point='pages', mount_label='Pages')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/trac/tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index 78d8d17..f7d50b4 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -69,11 +69,10 @@ class TracTicketImportController(BaseController):
     @require_post()
     @validate(TracTicketImportSchema(), error_handler=index)
     def create(self, trac_url, mount_point, mount_label, **kw):
-        app = TracTicketImporter().import_tool(c.project,
+        app = TracTicketImporter().import_tool(c.project, c.user,
                 mount_point=mount_point,
                 mount_label=mount_label,
-                trac_url=trac_url,
-                user=c.user)
+                trac_url=trac_url)
         redirect(app.url())
 
 
@@ -84,8 +83,8 @@ class TracTicketImporter(ToolImporter):
     tool_label = 'Trac Ticket Importer'
     tool_description = 'Import your tickets from Trac'
 
-    def import_tool(self, project=None, mount_point=None, mount_label=None,
-            trac_url=None, user=None, **kw):
+    def import_tool(self, project, user, project_name=None, mount_point=None,
+            mount_label=None, trac_url=None, **kw):
         """ Import Trac tickets into a new Allura Tracker tool.
 
         """

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/trac/wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/wiki.py b/ForgeImporters/forgeimporters/trac/wiki.py
index 00f0d48..300b476 100644
--- a/ForgeImporters/forgeimporters/trac/wiki.py
+++ b/ForgeImporters/forgeimporters/trac/wiki.py
@@ -68,11 +68,10 @@ class TracWikiImportController(BaseController):
     @require_post()
     @validate(TracWikiImportSchema(), error_handler=index)
     def create(self, trac_url, mount_point, mount_label, **kw):
-        app = TracWikiImporter().import_tool(c.project,
+        app = TracWikiImporter().import_tool(c.project, c.user,
                 mount_point=mount_point,
                 mount_label=mount_label,
-                trac_url=trac_url,
-                user=c.user)
+                trac_url=trac_url)
         redirect(app.url())
 
 
@@ -83,8 +82,8 @@ class TracWikiImporter(ToolImporter):
     tool_label = 'Trac Wiki Importer'
     tool_description = 'Import your wiki from Trac'
 
-    def import_tool(self, project=None, mount_point=None, mount_label=None,
-            trac_url=None, user=None, **kw):
+    def import_tool(self, project, user, project_name=None, mount_point=None,
+            mount_label=None, trac_url=None, **kw):
         """ Import Trac wiki into a new Allura Wiki tool.
 
         """


[41/50] git commit: (quickfix) Fix shortname validator for project importers due to previous refactor

Posted by jo...@apache.org.
(quickfix) Fix shortname validator for project importers due to previous refactor


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

Branch: refs/heads/cj/6422
Commit: cb85a232a8b5759343b9a30c77d0f3cf79986bdc
Parents: 63e0935
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Wed Aug 7 20:01:29 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 7 20:01:29 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/base.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/cb85a232/ForgeImporters/forgeimporters/base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index 3aea8c0..8cc3b52 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -27,7 +27,7 @@ from formencode import validators as fev, schema
 from allura.lib.decorators import require_post
 from allura.lib.decorators import task
 from allura.lib.security import require_access
-from allura.lib.widgets.forms import NeighborhoodProjectShortNameValidator
+from allura.lib.plugin import ProjectRegistrationProvider
 from allura.lib import exceptions
 
 from paste.deploy.converters import aslist
@@ -42,11 +42,12 @@ log = logging.getLogger(__name__)
 class ProjectImportForm(schema.Schema):
     def __init__(self, source):
         super(ProjectImportForm, self).__init__()
+        provider = ProjectRegistrationProvider.get()
         self.add_field('tools', ToolsValidator(source))
+        self.add_field('project_shortname', provider.shortname_validator)
 
     neighborhood = fev.PlainText(not_empty=True)
     project_name = fev.UnicodeString(not_empty=True, max=40)
-    project_shortname = NeighborhoodProjectShortNameValidator()
 
 
 @task


[07/50] git commit: [#6441] ScriptTask doc parser chokes on %d

Posted by jo...@apache.org.
[#6441] ScriptTask doc parser chokes on %d

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/53e35eb9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/53e35eb9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/53e35eb9

Branch: refs/heads/cj/6422
Commit: 53e35eb9fcc2ac8b9bdc6541404c0392414966cb
Parents: 7975c7c
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Wed Jul 31 13:17:36 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 13:17:36 2013 +0000

----------------------------------------------------------------------
 ForgeTracker/forgetracker/scripts/import_tracker.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/53e35eb9/ForgeTracker/forgetracker/scripts/import_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/scripts/import_tracker.py b/ForgeTracker/forgetracker/scripts/import_tracker.py
index a84bede..506e771 100644
--- a/ForgeTracker/forgetracker/scripts/import_tracker.py
+++ b/ForgeTracker/forgetracker/scripts/import_tracker.py
@@ -106,7 +106,7 @@ class ImportTracker(ScriptTask):
         parser.add_argument('-s', '--secret-key', action='store', dest='secret_key', help='Secret key')
         parser.add_argument('-p', '--project', action='store', dest='project', help='Project to import to')
         parser.add_argument('-t', '--tracker', action='store', dest='tracker', help='Tracker to import to')
-        parser.add_argument('-u', '--base-url', dest='base_url', default='https://sourceforge.net', help='Base Allura URL (%default)')
+        parser.add_argument('-u', '--base-url', dest='base_url', default='https://sourceforge.net', help='Base Allura URL (https://sourceforge.net)')
         parser.add_argument('-o', dest='import_opts', default=[], action='store',  help='Specify import option(s)', metavar='opt=val')
         parser.add_argument('--user-map', dest='user_map_file', help='Map original users to SF.net users', metavar='JSON_FILE')
         parser.add_argument('--file_data', dest='file_data', help='json file', metavar='JSON_FILE')


[26/50] git commit: [#6461] Partial implementation of GC Issues importer based on gdata API

Posted by jo...@apache.org.
[#6461] Partial implementation of GC Issues importer based on gdata API

The gdata API was shut off, so to avoid unnecessary work, most of the
GDataAPI classes are left unimplemented and untested, but the core logic
in GoogleCodeTrackerImporter is implemented and tested.

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/9cc5c8ff
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/9cc5c8ff
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/9cc5c8ff

Branch: refs/heads/cj/6422
Commit: 9cc5c8ffb0824e46b4519c7a07fc9914d0568f3b
Parents: 93d12e6
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Mon Jul 29 00:15:16 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Tue Aug 6 21:49:17 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/google/__init__.py           |   5 +-
 ForgeImporters/forgeimporters/google/code.py    |  13 +-
 ForgeImporters/forgeimporters/google/project.py |   4 +-
 ForgeImporters/forgeimporters/google/tasks.py   |   8 +-
 .../forgeimporters/google/tests/test_code.py    |  22 +-
 ForgeImporters/forgeimporters/google/tracker.py | 260 +++++++++++++++++++
 .../tests/google/test_extractor.py              |  12 +-
 .../forgeimporters/tests/google/test_tasks.py   |   8 +-
 .../forgeimporters/tests/google/test_tracker.py | 234 +++++++++++++++++
 ForgeImporters/setup.py                         |   1 +
 requirements-common.txt                         |   1 +
 11 files changed, 523 insertions(+), 45 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9cc5c8ff/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 17d724f..57e384b 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -56,10 +56,9 @@ class GoogleCodeProjectExtractor(object):
 
     DEFAULT_ICON = 'http://www.gstatic.com/codesite/ph/images/defaultlogo.png'
 
-    def __init__(self, project, page='project_info'):
-        gc_project_name = project.get_tool_data('google-code', 'project_name')
+    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.project = project
         self.page = BeautifulSoup(urllib2.urlopen(self.url))
 
     def get_short_description(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9cc5c8ff/ForgeImporters/forgeimporters/google/code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/code.py b/ForgeImporters/forgeimporters/google/code.py
index 8e047fb..ef7f800 100644
--- a/ForgeImporters/forgeimporters/google/code.py
+++ b/ForgeImporters/forgeimporters/google/code.py
@@ -86,8 +86,8 @@ class GoogleRepoImportController(BaseController):
     @require_post()
     @validate(GoogleRepoImportSchema(), error_handler=index)
     def create(self, gc_project_name, mount_point, mount_label, **kw):
-        c.project.set_tool_data('google-code', project_name=gc_project_name)
         app = GoogleRepoImporter.import_tool(c.project,
+                project_name=gc_project_name,
                 mount_point=mount_point,
                 mount_label=mount_label)
         redirect(app.url())
@@ -100,18 +100,13 @@ class GoogleRepoImporter(ToolImporter):
     tool_label = 'Google Code Source Importer'
     tool_description = 'Import your SVN, Git, or Hg repo from Google Code'
 
-    def import_tool(self, project=None, mount_point=None, mount_label=None):
+    def import_tool(self, project, project_name, mount_point=None, mount_label=None):
         """ Import a Google Code repo into a new SVN, Git, or Hg Allura tool.
 
         """
-        if not project:
-            raise Exception("You must supply a project")
-        if not project.get_tool_data('google-code', 'project_name'):
-            raise Exception("Missing Google Code project name")
-        extractor = GoogleCodeProjectExtractor(project, page='source_browse')
+        extractor = GoogleCodeProjectExtractor(project, project_name, 'source_browse')
         repo_type = extractor.get_repo_type()
-        repo_url = get_repo_url(project.get_tool_data('google-code',
-            'project_name'), repo_type)
+        repo_url = get_repo_url(project_name, repo_type)
         app = project.install_app(
                 REPO_ENTRY_POINTS[repo_type],
                 mount_point=mount_point or 'code',

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9cc5c8ff/ForgeImporters/forgeimporters/google/project.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/project.py b/ForgeImporters/forgeimporters/google/project.py
index d579606..7416258 100644
--- a/ForgeImporters/forgeimporters/google/project.py
+++ b/ForgeImporters/forgeimporters/google/project.py
@@ -90,9 +90,9 @@ class GoogleCodeProjectImporter(base.ProjectImporter):
             redirect('.')
 
         c.project.set_tool_data('google-code', project_name=project_name)
-        tasks.import_project_info.post()
+        tasks.import_project_info.post(project_name)
         for importer_name in tools:
-            tasks.import_tool.post(importer_name)
+            tasks.import_tool.post(importer_name, project_name)
 
         flash('Welcome to the %s Project System! '
               'Your project data will be imported and should show up here shortly.' % config['site_name'])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9cc5c8ff/ForgeImporters/forgeimporters/google/tasks.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tasks.py b/ForgeImporters/forgeimporters/google/tasks.py
index 65dd126..3e6e74d 100644
--- a/ForgeImporters/forgeimporters/google/tasks.py
+++ b/ForgeImporters/forgeimporters/google/tasks.py
@@ -27,8 +27,8 @@ from ..base import ToolImporter
 
 
 @task
-def import_project_info():
-    extractor = GoogleCodeProjectExtractor(c.project, 'project_info')
+def import_project_info(project_name):
+    extractor = GoogleCodeProjectExtractor(c.project, project_name, 'project_info')
     extractor.get_short_description()
     extractor.get_icon()
     extractor.get_license()
@@ -36,6 +36,6 @@ def import_project_info():
     g.post_event('project_updated')
 
 @task
-def import_tool(importer_name, mount_point=None, mount_label=None):
+def import_tool(importer_name, project_name, mount_point=None, mount_label=None):
     importer = ToolImporter.by_name(importer_name)
-    importer.import_tool(c.project, mount_point, mount_label)
+    importer.import_tool(c.project, project_name, mount_point, mount_label)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9cc5c8ff/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 cc91178..fe6943b 100644
--- a/ForgeImporters/forgeimporters/google/tests/test_code.py
+++ b/ForgeImporters/forgeimporters/google/tests/test_code.py
@@ -56,30 +56,20 @@ class TestGoogleRepoImporter(TestCase):
         project.get_tool_data.side_effect = lambda *args: gc_proj_name
         return project
 
-    @patch('forgeimporters.google.code.GoogleCodeProjectExtractor.get_repo_type')
+    @patch('forgeimporters.google.code.GoogleCodeProjectExtractor')
     @patch('forgeimporters.google.code.get_repo_url')
-    def test_import_tool_happy_path(self, get_repo_url, get_repo_type):
-        get_repo_type.return_value = 'git'
+    def test_import_tool_happy_path(self, get_repo_url, gcpe):
+        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')
-        GoogleRepoImporter().import_tool(p)
+        GoogleRepoImporter().import_tool(p, 'project_name')
+        get_repo_url.assert_called_once_with('project_name', 'git')
         p.install_app.assert_called_once_with('Git',
                 mount_point='code',
                 mount_label='Code',
                 init_from_url='http://remote/clone/url/',
                 )
 
-    def test_no_project(self):
-        with self.assertRaises(Exception) as cm:
-            GoogleRepoImporter().import_tool()
-        self.assertEqual(str(cm.exception), "You must supply a project")
-
-    def test_no_google_code_project_name(self):
-        p = self._make_project()
-        with self.assertRaises(Exception) as cm:
-            GoogleRepoImporter().import_tool(p)
-        self.assertEqual(str(cm.exception), "Missing Google Code project name")
-
 
 class TestGoogleRepoImportController(TestController, TestCase):
     def setUp(self):
@@ -110,8 +100,6 @@ class TestGoogleRepoImportController(TestController, TestCase):
                 status=302)
         project = M.Project.query.get(shortname=test_project_with_repo)
         self.assertEqual(r.location, 'http://localhost/p/{}/mymount'.format(test_project_with_repo))
-        self.assertEqual(project.get_tool_data('google-code', 'project_name'),
-                'poop')
         self.assertEqual(project._id, gri.import_tool.call_args[0][0]._id)
         self.assertEqual(u'mymount', gri.import_tool.call_args[1]['mount_point'])
         self.assertEqual(u'mylabel', gri.import_tool.call_args[1]['mount_label'])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9cc5c8ff/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
new file mode 100644
index 0000000..602690e
--- /dev/null
+++ b/ForgeImporters/forgeimporters/google/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 collections import defaultdict
+from datetime import datetime
+
+from pylons import tmpl_context as c
+import gdata
+from ming.orm import session
+
+from allura.lib import helpers as h
+
+from forgetracker.tracker_main import ForgeTrackerApp
+from forgetracker import model as TM
+from ..base import ToolImporter
+
+
+class GoogleCodeTrackerImporter(ToolImporter):
+    source = 'Google Code'
+    target_app = ForgeTrackerApp
+    controller = None
+    tool_label = 'Issues'
+
+    field_types = defaultdict(lambda: 'string',
+            milestone='milestone',
+            priority='select',
+            type='select',
+        )
+
+    def import_tool(self, project, project_name, mount_point=None, mount_label=None):
+        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']
+        self.custom_fields = {}
+        extractor = GDataAPIExtractor(project_name)
+        for issue in extractor.iter_issues():
+            ticket = TM.Ticket.new()
+            self.process_fields(ticket, issue)
+            self.process_labels(ticket, issue)
+            self.process_comments(ticket, extractor.iter_comments(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)
+
+    def custom_field(self, name):
+        if name not in self.custom_fields:
+            self.custom_fields[name] = {
+                    'type': self.field_types[name.lower()],
+                    'label': name,
+                    'name': u'_%s' % name.lower(),
+                    'options': set(),
+                }
+        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, '')
+
+    def process_labels(self, ticket, issue):
+        labels = set()
+        custom_fields = defaultdict(set)
+        for label in issue.labels:
+            if u'-' in label:
+                name, value = label.split(u'-', 1)
+                cf = self.custom_field(name)
+                cf['options'].add(value)
+                custom_fields[cf['name']].add(value)
+            else:
+                labels.add(label)
+        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(
+                    text = (
+                        u'Originally posted by: [{author.name}]({author.link})\n'
+                        '\n'
+                        '{body}\n'
+                        '\n'
+                        '{updates}').format(
+                            author=comment.author,
+                            body=comment.text,
+                            updates='\n'.join(
+                                '*%s*: %s' % (k,v)
+                                for k,v in comment.updates.items()
+                            ),
+                    )
+                )
+            p.add_multiple_attachments(comment.attachments)
+
+    def postprocess_custom_fields(self):
+        c.app.globals.custom_fields = []
+        for name, field in self.custom_fields.iteritems():
+            if field['name'] == '_milestone':
+                field['milestones'] = [{
+                        'name': milestone,
+                        'due_date': None,
+                        'complete': False,
+                    } for milestone in field['options']]
+                field['options'] = ''
+            elif field['type'] == 'select':
+                field['options'] = ' '.join(field['options'])
+            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)
+            issues = client.get_comments(self.project_name, query=query).entry
+            if len(issues) <= 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/9cc5c8ff/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 e346f1e..1a3a87c 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -36,16 +36,15 @@ class TestGoogleCodeProjectExtractor(TestCase):
         self._p_soup.stop()
 
     def test_init(self):
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'project_info')
+        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
 
-        self.project.get_tool_data.assert_called_once_with('google-code', 'project_name')
         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)
 
     def test_get_short_description(self):
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'project_info')
+        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
         extractor.page.find.return_value.string = 'My Super Project'
 
         extractor.get_short_description()
@@ -57,7 +56,7 @@ 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, 'project_info')
+        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
         extractor.page.find.return_value.attrMap = {'src': 'http://example.com/foo/bar/my-logo.png'}
         self.urlopen.reset_mock()
 
@@ -75,7 +74,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
     @mock.patch.object(google, 'M')
     def test_get_license(self, M):
         self.project.trove_license = []
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'project_info')
+        extractor = google.GoogleCodeProjectExtractor(self.project, '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
 
@@ -94,7 +93,8 @@ class TestGoogleCodeProjectExtractor(TestCase):
 
     def _make_extractor(self, html):
         from BeautifulSoup import BeautifulSoup
-        extractor = google.GoogleCodeProjectExtractor(self.project)
+        with mock.patch.object(google, 'urllib2') as urllib2:
+            extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
         extractor.page = BeautifulSoup(html)
         extractor.url="http://test/source/browse"
         return extractor

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9cc5c8ff/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 bb9319d..23da83f 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tasks.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tasks.py
@@ -25,8 +25,8 @@ from ...google import tasks
 @mock.patch.object(tasks, 'c')
 def test_import_project_info(c, session, gpe):
     c.project = mock.Mock(name='project')
-    tasks.import_project_info()
-    gpe.assert_called_once_with(c.project, 'project_info')
+    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()
@@ -37,6 +37,6 @@ def test_import_project_info(c, session, gpe):
 @mock.patch.object(tasks, 'c')
 def test_import_tool(c, by_name):
     c.project = mock.Mock(name='project')
-    tasks.import_tool('importer_name', 'mount_point', 'mount_label')
+    tasks.import_tool('importer_name', 'project_name', 'mount_point', 'mount_label')
     by_name.assert_called_once_with('importer_name')
-    by_name.return_value.import_tool.assert_called_once_with(c.project, 'mount_point', 'mount_label')
+    by_name.return_value.import_tool.assert_called_once_with(c.project, 'project_name', 'mount_point', 'mount_label')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9cc5c8ff/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
new file mode 100644
index 0000000..d54ac90
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -0,0 +1,234 @@
+#       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 operator import itemgetter
+from unittest import TestCase
+import mock
+
+from ...google import tracker
+
+
+class TestTrackerImporter(TestCase):
+    @mock.patch.object(tracker, 'c')
+    @mock.patch.object(tracker, 'session')
+    @mock.patch.object(tracker, 'TM')
+    @mock.patch.object(tracker, 'GDataAPIExtractor')
+    def test_import_tool(self, gdata, TM, session, c):
+        importer = tracker.GoogleCodeTrackerImporter()
+        importer.process_fields = mock.Mock()
+        importer.process_labels = mock.Mock()
+        importer.process_comments = mock.Mock()
+        importer.postprocess_custom_fields = mock.Mock()
+        project = mock.Mock()
+        app = project.install_app.return_value
+        extractor = gdata.return_value
+        issues = extractor.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, 'project_name', 'mount_point', 'mount_label')
+
+        project.install_app.assert_called_once_with('tracker', 'mount_point', 'mount_label')
+        gdata.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]),
+            ])
+        self.assertEqual(importer.process_labels.call_args_list, [
+                mock.call(tickets[0], issues[0]),
+                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]),
+            ])
+        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(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]),
+                mock.call(tickets[1]),
+            ])
+
+    def test_custom_fields(self):
+        importer = tracker.GoogleCodeTrackerImporter()
+        importer.custom_fields = {}
+        importer.custom_field('Foo')
+        importer.custom_field('Milestone')
+        importer.custom_field('Priority')
+        importer.custom_field('Type')
+        self.assertEqual(importer.custom_fields, {
+                'Foo': {
+                        'type': 'string',
+                        'label': 'Foo',
+                        'name': '_foo',
+                        'options': set(),
+                    },
+                'Milestone': {
+                        'type': 'milestone',
+                        'label': 'Milestone',
+                        'name': '_milestone',
+                        'options': set(),
+                    },
+                'Priority': {
+                        'type': 'select',
+                        'label': 'Priority',
+                        'name': '_priority',
+                        'options': set(),
+                    },
+                'Type': {
+                        'type': 'select',
+                        'label': 'Type',
+                        'name': '_type',
+                        'options': set(),
+                    },
+            })
+        importer.custom_fields = {'Foo': {}}
+        importer.custom_field('Foo')
+        self.assertEqual(importer.custom_fields, {'Foo': {}})
+
+    def test_process_fields(self):
+        ticket = mock.Mock()
+        issue = mock.Mock(
+                summary='summary',
+                description='description',
+                status='status',
+                created_date='created_date',
+                mod_date='mod_date',
+            )
+        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.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', ''),
+                ])
+
+    def test_process_labels(self):
+        ticket = mock.Mock(custom_fields={}, labels=[])
+        issue = mock.Mock(labels=['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)
+        self.assertEqual(ticket.labels, ['Baz'])
+        self.assertEqual(ticket.custom_fields, {'_foo': 'Bar, Qux'})
+
+    def test_process_comments(self):
+        def _author(n):
+            a = mock.Mock()
+            a.name = 'author%s' % n
+            a.link = 'author%s_link' % n
+            return a
+        ticket = mock.Mock()
+        comments = [
+                mock.Mock(
+                    author=_author(1),
+                    text='text1',
+                    attachments='attachments1',
+                ),
+                mock.Mock(
+                    author=_author(2),
+                    text='text2',
+                    attachments='attachments2',
+                ),
+            ]
+        comments[0].updates.items.return_value = [('Foo', 'Bar'), ('Baz', 'Qux')]
+        comments[1].updates.items.return_value = []
+        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'
+                '\n'
+                'text1\n'
+                '\n'
+                '*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'
+                '\n'
+                'text2\n'
+                '\n'
+            ))
+        self.assertEqual(ticket.thread.add_post.return_value.add_multiple_attachments.call_args_list, [
+                mock.call('attachments1'),
+                mock.call('attachments2'),
+            ])
+
+    @mock.patch.object(tracker, 'c')
+    def test_postprocess_custom_fields(self, c):
+        importer = tracker.GoogleCodeTrackerImporter()
+        importer.custom_fields = {
+                'Foo': {
+                    'name': '_foo',
+                    'type': 'string',
+                    'options': set(['foo', 'bar']),
+                },
+                'Milestone': {
+                    'name': '_milestone',
+                    'type': 'milestone',
+                    'options': set(['foo', 'bar']),
+                },
+                'Priority': {
+                    'name': '_priority',
+                    'type': 'select',
+                    'options': set(['foo', 'bar']),
+                },
+            }
+        importer.postprocess_custom_fields()
+        self.assertEqual(sorted(c.app.globals.custom_fields, key=itemgetter('name')), [
+                {
+                    'name': '_foo',
+                    'type': 'string',
+                    'options': '',
+                },
+                {
+                    'name': '_milestone',
+                    'type': 'milestone',
+                    'options': '',
+                    'milestones': [
+                        {'name': 'foo', 'due_date': None, 'complete': False},
+                        {'name': 'bar', 'due_date': None, 'complete': False},
+                    ],
+                },
+                {
+                    'name': '_priority',
+                    'type': 'select',
+                    'options': 'foo bar',
+                },
+            ])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9cc5c8ff/ForgeImporters/setup.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/setup.py b/ForgeImporters/setup.py
index 8af3c1a..45a08eb 100644
--- a/ForgeImporters/setup.py
+++ b/ForgeImporters/setup.py
@@ -37,5 +37,6 @@ setup(name='ForgeImporters',
       google-code = forgeimporters.google.project:GoogleCodeProjectImporter
 
       [allura.importers]
+      google-code-tracker = forgeimporters.google.tracker:GoogleCodeTrackerImporter
       google-code-repo = forgeimporters.google.code:GoogleRepoImporter
       """,)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9cc5c8ff/requirements-common.txt
----------------------------------------------------------------------
diff --git a/requirements-common.txt b/requirements-common.txt
index 5e261a0..e7b7ef1 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -50,6 +50,7 @@ TurboGears2==2.1.5
 WebOb==1.0.8
 # part of the stdlib, but with a version number.  see http://guide.python-distribute.org/pip.html#listing-installed-packages
 wsgiref==0.1.2
+gdata==2.0.18
 
 # tg2 deps (not used directly)
 Babel==0.9.6


[47/50] 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/6422
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():


[08/50] git commit: [#6460] Fixed security checks sometimes using incorrect roles

Posted by jo...@apache.org.
[#6460] Fixed security checks sometimes using incorrect roles

When doing a has_access() check for a given user against a given
artifact without explicitly specifying the project, c.project was being
used to get the list of user's roles instead of the artifact's project
attribute.  If c.project was not the project to which the artifact
belonged, the the wrong set of role_ids were being used, resulting in
access being denied.  It's a bit nonsensical to use an unrelated
project's role_ids to check access to an artifact, and this was breaking
notifications, which fire all pending notifications, regardless of the
context under which fire_ready() was called.

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/4b2d8c17
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/4b2d8c17
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/4b2d8c17

Branch: refs/heads/cj/6422
Commit: 4b2d8c171aedf12c525d659470b0d807732afb9d
Parents: 53e35eb
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Mon Jul 29 20:40:35 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 13:50:42 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/security.py                  |  4 +-
 Allura/allura/model/notification.py            |  6 +-
 Allura/allura/tests/model/test_notification.py | 86 +++++++++++++++++++++
 Allura/allura/tests/test_security.py           | 21 +++++
 4 files changed, 113 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4b2d8c17/Allura/allura/lib/security.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/security.py b/Allura/allura/lib/security.py
index 21ffb0a..a39e68d 100644
--- a/Allura/allura/lib/security.py
+++ b/Allura/allura/lib/security.py
@@ -287,7 +287,9 @@ def has_access(obj, permission, user=None, project=None):
                 elif isinstance(obj, M.Project):
                     project = obj.root_project
                 else:
-                    project = c.project.root_project
+                    project = getattr(obj, 'project', None)
+                    if project is None:
+                        project = c.project.root_project
             roles = cred.user_roles(user_id=user._id, project_id=project._id).reaching_ids
         chainable_roles = []
         for rid in roles:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4b2d8c17/Allura/allura/model/notification.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/notification.py b/Allura/allura/model/notification.py
index d4b0755..f1449fe 100644
--- a/Allura/allura/model/notification.py
+++ b/Allura/allura/model/notification.py
@@ -250,10 +250,10 @@ class Notification(MappedClass):
                 not security.has_access(artifact, 'read', user)():
             log.debug("Skipping notification - User %s doesn't have read "
                       "access to artifact %s" % (user_id, str(self.ref_id)))
-            log.debug("User roles [%s]; artifact ACL [%s]; project ACL [%s]",
-                    ', '.join([str(r) for r in security.Credentials.get().user_roles(user_id=user_id, project_id=c.project._id).reaching_ids]),
+            log.debug("User roles [%s]; artifact ACL [%s]; PSC ACL [%s]",
+                    ', '.join([str(r) for r in security.Credentials.get().user_roles(user_id=user_id, project_id=artifact.project._id).reaching_ids]),
                     ', '.join([str(a) for a in artifact.acl]),
-                    ', '.join([str(a) for a in c.project.acl]))
+                    ', '.join([str(a) for a in artifact.parent_security_context().acl]))
             return
         allura.tasks.mail_tasks.sendmail.post(
             destinations=[str(user_id)],

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4b2d8c17/Allura/allura/tests/model/test_notification.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/model/test_notification.py b/Allura/allura/tests/model/test_notification.py
index 4e9dd5b..1e1c273 100644
--- a/Allura/allura/tests/model/test_notification.py
+++ b/Allura/allura/tests/model/test_notification.py
@@ -68,6 +68,92 @@ class TestNotification(unittest.TestCase):
         assert len(subscriptions) == 0
         assert M.Mailbox.query.find().count() == 0
 
+    @mock.patch('allura.tasks.mail_tasks.sendmail')
+    def test_send_direct(self, sendmail):
+        c.user = M.User.query.get(username='test-user')
+        wiki = c.project.app_instance('wiki')
+        page = WM.Page.query.get(app_config_id=wiki.config._id)
+        notification = M.Notification(
+                _id='_id',
+                ref=page.ref,
+                from_address='from_address',
+                reply_to_address='reply_to_address',
+                in_reply_to='in_reply_to',
+                subject='subject',
+                text='text',
+            )
+        notification.footer = lambda: ' footer'
+        notification.send_direct(c.user._id)
+        sendmail.post.assert_called_once_with(
+                destinations=[str(c.user._id)],
+                fromaddr='from_address',
+                reply_to='reply_to_address',
+                subject='subject',
+                message_id='_id',
+                in_reply_to='in_reply_to',
+                text='text footer',
+            )
+
+    @mock.patch('allura.tasks.mail_tasks.sendmail')
+    def test_send_direct_no_access(self, sendmail):
+        c.user = M.User.query.get(username='test-user')
+        wiki = c.project.app_instance('wiki')
+        page = WM.Page.query.get(app_config_id=wiki.config._id)
+        page.parent_security_context().acl = []
+        ThreadLocalORMSession.flush_all()
+        ThreadLocalORMSession.close_all()
+        notification = M.Notification(
+                _id='_id',
+                ref=page.ref,
+                from_address='from_address',
+                reply_to_address='reply_to_address',
+                in_reply_to='in_reply_to',
+                subject='subject',
+                text='text',
+            )
+        notification.footer = lambda: ' footer'
+        notification.send_direct(c.user._id)
+        assert_equal(sendmail.post.call_count, 0)
+
+    @mock.patch('allura.tasks.mail_tasks.sendmail')
+    def test_send_direct_wrong_project_context(self, sendmail):
+        """
+        Test that Notification.send_direct() works as expected even
+        if c.project is wrong.
+
+        This can happen when a notify task is triggered on project A (thus
+        setting c.project to A) and then calls Mailbox.fire_ready() which fires
+        pending Notifications on any waiting Mailbox, regardless of project,
+        but doesn't update c.project.
+        """
+        project1 = c.project
+        project2 = M.Project.query.get(shortname='test2')
+        assert_equal(project1.shortname, 'test')
+        c.user = M.User.query.get(username='test-user')
+        wiki = project1.app_instance('wiki')
+        page = WM.Page.query.get(app_config_id=wiki.config._id)
+        notification = M.Notification(
+                _id='_id',
+                ref=page.ref,
+                from_address='from_address',
+                reply_to_address='reply_to_address',
+                in_reply_to='in_reply_to',
+                subject='subject',
+                text='text',
+            )
+        notification.footer = lambda: ' footer'
+        c.project = project2
+        notification.send_direct(c.user._id)
+        sendmail.post.assert_called_once_with(
+                destinations=[str(c.user._id)],
+                fromaddr='from_address',
+                reply_to='reply_to_address',
+                subject='subject',
+                message_id='_id',
+                in_reply_to='in_reply_to',
+                text='text footer',
+            )
+
 class TestPostNotifications(unittest.TestCase):
 
     def setUp(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4b2d8c17/Allura/allura/tests/test_security.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_security.py b/Allura/allura/tests/test_security.py
index 40f9b8b..10ae614 100644
--- a/Allura/allura/tests/test_security.py
+++ b/Allura/allura/tests/test_security.py
@@ -136,3 +136,24 @@ class TestSecurity(TestController):
         assert has_access(wiki, 'post', test_user)()
         assert has_access(wiki, 'unmoderated_post', test_user)()
         assert_equal(all_allowed(wiki, test_user), set(['read', 'post', 'unmoderated_post']))
+
+    @td.with_wiki
+    def test_implicit_project(self):
+        '''
+        Test that relying on implicit project context does the Right Thing.
+
+        If you call has_access(artifact, perm), it should use the roles from
+        the project to which artifact belongs, even in c.project is something
+        else.  If you really want to use the roles from an unrelated project,
+        you should have to be very explicit about it, not just set c.project.
+        '''
+        project1 = c.project
+        project2 = M.Project.query.get(shortname='test2')
+        wiki = project1.app_instance('wiki')
+        page = WM.Page.query.get(app_config_id=wiki.config._id)
+        test_user = M.User.by_username('test-user')
+
+        assert_equal(project1.shortname, 'test')
+        assert has_access(page, 'read', test_user)()
+        c.project = project2
+        assert has_access(page, 'read', test_user)()


[30/50] git commit: [#6480] Add trac project and wiki importers; refactor bases

Posted by jo...@apache.org.
[#6480] Add trac project and wiki importers; refactor bases

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/63fdf19f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/63fdf19f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/63fdf19f

Branch: refs/heads/cj/6422
Commit: 63fdf19fda232d6b0774ee5c428f3fed1b80cd21
Parents: 512ee4b
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Fri Aug 2 22:01:08 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:23 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/base.py           |  96 +++++++++++++++-
 ForgeImporters/forgeimporters/google/project.py |  66 +++--------
 ForgeImporters/forgeimporters/google/tasks.py   |   6 -
 .../google/templates/project.html               |  62 -----------
 .../forgeimporters/templates/project_base.html  |  89 ++++++++++++++-
 ForgeImporters/forgeimporters/trac/project.py   |  63 +++++++++++
 .../forgeimporters/trac/templates/project.html  |  32 ++++++
 .../trac/templates/wiki/index.html              |  42 +++++++
 ForgeImporters/forgeimporters/trac/tickets.py   |   2 +-
 ForgeImporters/forgeimporters/trac/wiki.py      | 110 +++++++++++++++++++
 ForgeImporters/setup.py                         |   3 +
 11 files changed, 445 insertions(+), 126 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index 034a580..5652474 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -15,16 +15,46 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+import logging
+
 from pkg_resources import iter_entry_points
 
-from tg import expose
+from tg import expose, validate, flash, redirect, config
+from tg.decorators import with_trailing_slash
+from pylons import tmpl_context as c
+from formencode import validators as fev, schema
+
+from allura.lib.decorators import require_post
+from allura.lib.decorators import task
+from allura.lib.security import require_access
+from allura.lib.widgets.forms import NeighborhoodProjectShortNameValidator
+from allura.lib import exceptions
+
 from paste.deploy.converters import aslist
-from formencode import validators as fev
 
 from ming.utils import LazyProperty
 from allura.controllers import BaseController
 
 
+log = logging.getLogger(__name__)
+
+
+class ProjectImportForm(schema.Schema):
+    def __init__(self, source):
+        super(ProjectImportForm, self).__init__()
+        self.add_field('tools', ToolsValidator(source))
+
+    neighborhood = fev.PlainText(not_empty=True)
+    project_name = fev.UnicodeString(not_empty=True, max=40)
+    project_shortname = NeighborhoodProjectShortNameValidator()
+
+
+@task
+def import_tool(importer_name, project_name, mount_point=None, mount_label=None, **kw):
+    importer = ToolImporter.by_name(importer_name)
+    importer.import_tool(c.project, mount_point, mount_label, **kw)
+
+
 class ProjectImporter(BaseController):
     """
     Base class for project importers.
@@ -33,6 +63,14 @@ class ProjectImporter(BaseController):
     :meth:`process()` views described below.
     """
     source = None
+    process_validator = None
+    index_template = None
+
+    def __init__(self, neighborhood, *a, **kw):
+        self.neighborhood = neighborhood
+
+    def _check_security(self):
+        require_access(self.neighborhood, 'register')
 
     @LazyProperty
     def tool_importers(self):
@@ -47,6 +85,8 @@ class ProjectImporter(BaseController):
                 tools[ep.name] = epv()
         return tools
 
+    @with_trailing_slash
+    @expose()
     def index(self, **kw):
         """
         Override and expose this view to present the project import form.
@@ -58,9 +98,12 @@ class ProjectImporter(BaseController):
         This will list the available tool importers.  Other project fields
         (e.g., project_name) should go in the project_fields block.
         """
-        raise NotImplemented
+        return {'importer': self, 'tg_template': self.index_template}
 
-    def process(self, tools=None, **kw):
+    @require_post()
+    @expose()
+    @validate(process_validator, error_handler=index)
+    def process(self, **kw):
         """
         Override and expose this to handle a project import.
 
@@ -68,7 +111,50 @@ class ProjectImporter(BaseController):
         tools installed and redirect to the new project, presumably with a
         message indicating that some data will not be available immediately.
         """
-        raise NotImplemented
+        try:
+            c.project = self.neighborhood.register_project(kw['project_shortname'],
+                    project_name=kw['project_name'])
+        except exceptions.ProjectOverlimitError:
+            flash("You have exceeded the maximum number of projects you are allowed to create", 'error')
+            redirect('.')
+        except exceptions.ProjectRatelimitError:
+            flash("Project creation rate limit exceeded.  Please try again later.", 'error')
+            redirect('.')
+        except Exception:
+            log.error('error registering project: %s', kw['project_shortname'], exc_info=True)
+            flash('Internal Error. Please try again later.', 'error')
+            redirect('.')
+
+        self.after_project_create(c.project, **kw)
+        for importer_name in kw['tools']:
+            import_tool.post(importer_name, **kw)
+
+        flash('Welcome to the %s Project System! '
+              'Your project data will be imported and should show up here shortly.' % config['site_name'])
+        redirect(c.project.script_name + 'admin/overview')
+
+    @expose('json:')
+    @validate(process_validator)
+    def check_names(self, **kw):
+        """
+        Ajax form validation.
+
+        """
+        return c.form_errors
+
+    def after_project_create(self, project, **kw):
+        """
+        Called after project is created.
+
+        Useful for doing extra processing on the project before individual
+        tool imports happen.
+
+        :param project: The newly created project.
+        :param \*\*kw: The keyword arguments that were posted to the controller
+            method that created the project.
+
+        """
+        pass
 
 
 class ToolImporter(object):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/google/project.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/project.py b/ForgeImporters/forgeimporters/google/project.py
index 7416258..dda41ea 100644
--- a/ForgeImporters/forgeimporters/google/project.py
+++ b/ForgeImporters/forgeimporters/google/project.py
@@ -17,16 +17,12 @@
 
 import logging
 
-from tg import expose, validate, flash, redirect, config
+from formencode import validators as fev
+
+from tg import expose, validate
 from tg.decorators import with_trailing_slash
-from pylons import tmpl_context as c
-from formencode import validators as fev, schema
 
 from allura.lib.decorators import require_post
-from allura.lib.widgets.forms import NeighborhoodProjectShortNameValidator
-from allura.lib.security import require_access
-from allura.lib import helpers as h
-from allura.lib import exceptions
 
 from .. import base
 from . import tasks
@@ -35,15 +31,12 @@ from . import tasks
 log = logging.getLogger(__name__)
 
 
-class GoogleCodeProjectForm(schema.Schema):
-    neighborhood = fev.PlainText(not_empty=True)
+class GoogleCodeProjectForm(base.ProjectImportForm):
     project_name = fev.Regex(r'^[a-z0-9][a-z0-9-]{,61}$',
             not_empty=True,
             messages={
                 'invalid': 'Please use only letters, numbers, and dashes.',
             })
-    project_shortname = NeighborhoodProjectShortNameValidator()
-    tools = base.ToolsValidator('Google Code')
 
 
 class GoogleCodeProjectImporter(base.ProjectImporter):
@@ -55,50 +48,25 @@ class GoogleCodeProjectImporter(base.ProjectImporter):
     import.
     """
     source = 'Google Code'
+    process_validator = GoogleCodeProjectForm(source)
+    index_template = 'jinja:forgeimporters.google:templates/project.html'
 
-    def __init__(self, neighborhood, *a, **kw):
-        super(GoogleCodeProjectImporter, self).__init__(*a, **kw)
-        self.neighborhood = neighborhood
-
-    def _check_security(self):
-        require_access(self.neighborhood, 'register')
+    def after_project_create(self, project, **kw):
+        project.set_tool_data('google-code', project_name=project.name)
+        tasks.import_project_info.post()
 
     @with_trailing_slash
-    @expose('jinja:forgeimporters.google:templates/project.html')
+    @expose(index_template)
     def index(self, **kw):
-        return {'importer': self}
+        return super(self.__class__, self).index(**kw)
 
     @require_post()
     @expose()
-    @validate(GoogleCodeProjectForm(), error_handler=index)
-    def process(self, project_name=None, project_shortname=None, tools=None, **kw):
-        project_name = h.really_unicode(project_name).encode('utf-8')
-        project_shortname = h.really_unicode(project_shortname).encode('utf-8').lower()
-
-        try:
-            c.project = self.neighborhood.register_project(project_shortname,
-                    project_name=project_name)
-        except exceptions.ProjectOverlimitError:
-            flash("You have exceeded the maximum number of projects you are allowed to create", 'error')
-            redirect('.')
-        except exceptions.ProjectRatelimitError:
-            flash("Project creation rate limit exceeded.  Please try again later.", 'error')
-            redirect('.')
-        except Exception as e:
-            log.error('error registering project: %s', project_shortname, exc_info=True)
-            flash('Internal Error. Please try again later.', 'error')
-            redirect('.')
-
-        c.project.set_tool_data('google-code', project_name=project_name)
-        tasks.import_project_info.post(project_name)
-        for importer_name in tools:
-            tasks.import_tool.post(importer_name, project_name)
-
-        flash('Welcome to the %s Project System! '
-              'Your project data will be imported and should show up here shortly.' % config['site_name'])
-        redirect(c.project.script_name + 'admin/overview')
+    @validate(process_validator, error_handler=index)
+    def process(self, **kw):
+        return super(self.__class__, self).process(**kw)
 
     @expose('json:')
-    @validate(GoogleCodeProjectForm())
-    def check_names(self, project_name=None, project_shortname=None, tools=None, **kw):
-        return c.form_errors
+    @validate(process_validator)
+    def check_names(self, **kw):
+        return super(self.__class__, self).check_names(**kw)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/google/tasks.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tasks.py b/ForgeImporters/forgeimporters/google/tasks.py
index 3e6e74d..968d9a9 100644
--- a/ForgeImporters/forgeimporters/google/tasks.py
+++ b/ForgeImporters/forgeimporters/google/tasks.py
@@ -23,7 +23,6 @@ from ming.orm import ThreadLocalORMSession
 from allura.lib.decorators import task
 
 from . import GoogleCodeProjectExtractor
-from ..base import ToolImporter
 
 
 @task
@@ -34,8 +33,3 @@ def import_project_info(project_name):
     extractor.get_license()
     ThreadLocalORMSession.flush_all()
     g.post_event('project_updated')
-
-@task
-def import_tool(importer_name, project_name, mount_point=None, mount_label=None):
-    importer = ToolImporter.by_name(importer_name)
-    importer.import_tool(c.project, project_name, mount_point, mount_label)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/google/templates/project.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/templates/project.html b/ForgeImporters/forgeimporters/google/templates/project.html
index 2cda0d0..172dcf5 100644
--- a/ForgeImporters/forgeimporters/google/templates/project.html
+++ b/ForgeImporters/forgeimporters/google/templates/project.html
@@ -18,68 +18,6 @@
 -#}
 {% extends 'forgeimporters:templates/project_base.html' %}
 
-{% block extra_css %}
-    {{ super() }}
-    <style type="text/css">
-        #project-import-form #project-fields input {
-            width: 88%;
-        }
-
-        .hidden { display: none; }
-    </style>
-{% endblock %}
-
-{% block extra_js %}
-    {{ super() }}
-    <script type="text/javascript">
-        var timers = {};
-        function delay(callback, ms) {
-            clearTimeout(timers[callback]);
-            timers[callback] = setTimeout(callback, ms);
-        }
-
-        var manual = false;
-        function suggest_name() {
-            var $project_shortname = $('#project_shortname');
-            if (!manual) {
-                $project_shortname.val($('#project_name').val());
-            }
-            $project_shortname.trigger('change');
-        }
-
-        function check_names() {
-            var data = {
-                'neighborhood': $('#neighborhood').val(),
-                'project_name': $('#project_name').val(),
-                'project_shortname': $('#project_shortname').val()
-            };
-            $.getJSON('check_names', data, function(result) {
-                $('#project_name_error').addClass('hidden');
-                $('#project_shortname_error').addClass('hidden');
-                for(var field in result) {
-                    $('#'+field+'_error').text(result[field]).removeClass('hidden');
-                }
-            });
-        }
-
-        function update_url() {
-            $('#url-fragment').text($('#project_shortname').val());
-        }
-
-        $(function() {
-            $('#project_name').focus().bind('change keyup', suggest_name);
-
-            $('#project_shortname').bind('change keyup', function(event) {
-                if (event.type == 'keyup') {
-                    manual = true;
-                }
-                update_url();
-                delay(check_names, 500);
-            });
-        });
-    </script>
-{% endblock %}
-
 {% block project_fields %}
     <div class="grid-6">
         <label>Google Project Name</label>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/templates/project_base.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/templates/project_base.html b/ForgeImporters/forgeimporters/templates/project_base.html
index fc654f2..d4db30e 100644
--- a/ForgeImporters/forgeimporters/templates/project_base.html
+++ b/ForgeImporters/forgeimporters/templates/project_base.html
@@ -23,13 +23,97 @@
 {% block title %}{{importer.source}} Project Importer{% endblock %}
 {% block header %}{{importer.source}} Project Importer{% endblock %}
 
-{% block content %}
+{% block extra_css %}
+    {{ super() }}
+    <style type="text/css">
+        #project-import-form #project-fields input {
+            width: 88%;
+        }
+
+        .hidden { display: none; }
+    </style>
+{% endblock %}
+
+{% block extra_js %}
+    {{ super() }}
+    <script type="text/javascript">
+        var timers = {};
+        function delay(callback, ms) {
+            clearTimeout(timers[callback]);
+            timers[callback] = setTimeout(callback, ms);
+        }
+
+        var manual = false;
+        function suggest_name() {
+            var $project_shortname = $('#project_shortname');
+            if (!manual) {
+                $project_shortname.val($('#project_name').val());
+            }
+            $project_shortname.trigger('change');
+        }
+
+        function check_names() {
+            var data = {
+                'neighborhood': $('#neighborhood').val(),
+                'project_name': $('#project_name').val(),
+                'project_shortname': $('#project_shortname').val()
+            };
+            $.getJSON('check_names', data, function(result) {
+                $('#project_name_error').addClass('hidden');
+                $('#project_shortname_error').addClass('hidden');
+                for(var field in result) {
+                    $('#'+field+'_error').text(result[field]).removeClass('hidden');
+                }
+            });
+        }
+
+        function update_url() {
+            $('#url-fragment').text($('#project_shortname').val());
+        }
 
+        $(function() {
+            $('#project_name').focus().bind('change keyup', suggest_name);
+
+            $('#project_shortname').bind('change keyup', function(event) {
+                if (event.type == 'keyup') {
+                    manual = true;
+                }
+                update_url();
+                delay(check_names, 500);
+            });
+        });
+    </script>
+{% endblock %}
+
+{% block content %}
 <form id="project-import-form" method="POST" action="process">
     <input type="hidden" id="neighborhood" name="neighborhood" value="{{importer.neighborhood.name}}"/>
 
     <fieldset id="project-fields">
-        {% block project_fields %}{% endblock %}
+      {% block project_fields %}
+      <div class="grid-6" style="clear:left">
+          <label>Project Name</label>
+      </div>
+      <div class="grid-10">
+          <input id="project_name" name="project_name" value="{{c.form_values['project_name']}}"/>
+          <div id="project_name_error" class="error{% if not c.form_errors['project_name'] %} hidden{% endif %}">
+              {{c.form_errors['project_name']}}
+          </div>
+      </div>
+
+      <div class="grid-6" style="clear:left">
+          <label>URL Name</label>
+      </div>
+      <div class="grid-10">
+          <input id="project_shortname" name="project_shortname" value="{{c.form_values['project_shortname']}}"/>
+          <div id="project_shortname_error" class="error{% if not c.form_errors['project_shortname'] %} hidden{% endif %}">
+              {{c.form_errors['project_shortname']}}
+          </div>
+          <div id="project-url">
+              http://{{request.environ['HTTP_HOST']}}{{importer.neighborhood.url()}}<span id="url-fragment">{{c.form_values['project_shortname']}}</span>
+          </div>
+      </div>
+      {% endblock %}
     </fieldset>
 
     <fieldset id="tool-fields">
@@ -50,5 +134,4 @@
 
     <input type="submit" value="Import"/>
 </form>
-
 {% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/trac/project.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/project.py b/ForgeImporters/forgeimporters/trac/project.py
new file mode 100644
index 0000000..66e8326
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/project.py
@@ -0,0 +1,63 @@
+#       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.
+
+import logging
+
+from formencode import validators as fev
+
+from tg import expose, validate
+from tg.decorators import with_trailing_slash
+
+from allura.lib.decorators import require_post
+
+from .. import base
+
+
+log = logging.getLogger(__name__)
+
+
+class TracProjectForm(base.ProjectImportForm):
+    trac_url = fev.URL(not_empty=True)
+
+
+class TracProjectImporter(base.ProjectImporter):
+    """
+    Project importer for Trac.
+
+    """
+    source = 'Trac'
+    process_validator = TracProjectForm(source)
+    index_template = 'jinja:forgeimporters.trac:templates/project.html'
+
+    def after_project_create(self, project, **kw):
+        project.set_tool_data('trac', url=kw['trac_url'])
+
+    @with_trailing_slash
+    @expose(index_template)
+    def index(self, **kw):
+        return super(self.__class__, self).index(**kw)
+
+    @require_post()
+    @expose()
+    @validate(process_validator, error_handler=index)
+    def process(self, **kw):
+        return super(self.__class__, self).process(**kw)
+
+    @expose('json:')
+    @validate(process_validator)
+    def check_names(self, **kw):
+        return super(self.__class__, self).check_names(**kw)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/trac/templates/project.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/templates/project.html b/ForgeImporters/forgeimporters/trac/templates/project.html
new file mode 100644
index 0000000..abcfb48
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/templates/project.html
@@ -0,0 +1,32 @@
+{#-
+       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.
+-#}
+{% extends 'forgeimporters:templates/project_base.html' %}
+
+{% block project_fields %}
+    <div class="grid-6">
+        <label>Trac URL</label>
+    </div>
+    <div class="grid-10">
+        <input id="trac_url" name="trac_url" value="{{c.form_values['trac_url']}}"/>
+        <div id="trac_ur_errorl" class="error{% if not c.form_errors['trac_url'] %} hidden{% endif %}">
+            {{c.form_errors['trac_url']}}
+        </div>
+    </div>
+    {{ super() }}
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/templates/wiki/index.html b/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
new file mode 100644
index 0000000..6083b9c
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
@@ -0,0 +1,42 @@
+{#-
+       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.
+-#}
+{% extends g.theme.master %}
+
+{% block title %}
+{{c.project.name}} / Import Trac Wiki
+{% endblock %}
+
+{% block header %}
+Import wiki from Trac
+{% endblock %}
+
+{% block content %}
+<form action="create" method="post" class="pad">
+  <label for="trac_url">URL of the Trac instance</label>
+  <input name="trac_url" />
+
+  <label for="mount_label">Label</label>
+  <input name="mount_label" value="Source" />
+
+  <label for="mount_point">Mount Point</label>
+  <input name="mount_point" value="source" />
+
+  <input type="submit" />
+</form>
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/trac/tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index cc31741..969dfd2 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -48,7 +48,7 @@ from allura.scripts.trac_export import (
 
 from forgeimporters.base import ToolImporter
 from forgetracker.tracker_main import ForgeTrackerApp
-from forgetracker.script.import_tracker import import_tracker
+from forgetracker.scripts.import_tracker import import_tracker
 
 
 class TracTicketImportSchema(fe.Schema):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/trac/wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/wiki.py b/ForgeImporters/forgeimporters/trac/wiki.py
new file mode 100644
index 0000000..2417863
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/wiki.py
@@ -0,0 +1,110 @@
+#       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.
+
+import argparse
+from datetime import (
+        datetime,
+        timedelta,
+        )
+import tempfile
+
+import formencode as fe
+from formencode import validators as fev
+
+from pylons import tmpl_context as c
+from pylons import app_globals as g
+from tg import (
+        config,
+        expose,
+        redirect,
+        validate,
+        )
+from tg.decorators import (
+        with_trailing_slash,
+        without_trailing_slash,
+        )
+
+from allura.controllers import BaseController
+from allura.lib.decorators import require_post
+from allura.model import ApiTicket
+
+from forgeimporters.base import ToolImporter
+
+from forgewiki.scripts.wiki_from_trac.extractors import WikiExporter
+from forgewiki.scripts.wiki_from_trac.loaders import load_data
+from forgewiki.scripts.wiki_from_trac.wiki_from_trac import WikiFromTrac
+from forgewiki.wiki_main import ForgeWikiApp
+
+
+class TracWikiImportSchema(fe.Schema):
+    trac_url = fev.URL(not_empty=True)
+    mount_point = fev.UnicodeString()
+    mount_label = fev.UnicodeString()
+
+
+class TracWikiImportController(BaseController):
+    @with_trailing_slash
+    @expose('jinja:forgeimporters.trac:templates/wiki/index.html')
+    def index(self, **kw):
+        return {}
+
+    @without_trailing_slash
+    @expose()
+    @require_post()
+    @validate(TracWikiImportSchema(), error_handler=index)
+    def create(self, trac_url, mount_point, mount_label, **kw):
+        app = TracWikiImporter.import_tool(c.project,
+                mount_point=mount_point,
+                mount_label=mount_label,
+                trac_url=trac_url,
+                user=c.user)
+        redirect(app.url())
+
+
+class TracWikiImporter(ToolImporter):
+    target_app = ForgeWikiApp
+    source = 'Trac'
+    controller = TracWikiImportController
+    tool_label = 'Trac Wiki Importer'
+    tool_description = 'Import your wiki from Trac'
+
+    def import_tool(self, project=None, mount_point=None, mount_label=None,
+            trac_url=None, user=None):
+        """ Import Trac wiki into a new Allura Wiki tool.
+
+        """
+        mount_point = mount_point or 'wiki'
+        app = project.install_app(
+                'Wiki',
+                mount_point=mount_point,
+                mount_label=mount_label or 'Wiki',
+                )
+        api_ticket = ApiTicket(user_id=user._id,
+                capabilities={"import": ["Projects", project.shortname]},
+                expires=datetime.utcnow() + timedelta(minutes=60))
+        options = argparse.Namespace()
+        options.api_key = api_ticket.api_key
+        options.secret_key = api_ticket.secret_key
+        options.project = project.shortname
+        options.wiki = mount_point
+        options.base_url = config['base_url']
+        with tempfile.NamedTemporaryFile() as f:
+            WikiExporter(trac_url, options).export(f)
+            f.flush()
+            load_data(f.name, WikiFromTrac.parser(), options)
+        g.post_event('project_updated')
+        return app

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/setup.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/setup.py b/ForgeImporters/setup.py
index 45a08eb..c4776ba 100644
--- a/ForgeImporters/setup.py
+++ b/ForgeImporters/setup.py
@@ -35,8 +35,11 @@ setup(name='ForgeImporters',
       # -*- Entry points: -*-
       [allura.project_importers]
       google-code = forgeimporters.google.project:GoogleCodeProjectImporter
+      trac = forgeimporters.trac.project:TracProjectImporter
 
       [allura.importers]
       google-code-tracker = forgeimporters.google.tracker:GoogleCodeTrackerImporter
       google-code-repo = forgeimporters.google.code:GoogleRepoImporter
+      trac-tickets = forgeimporters.trac.tickets:TracTicketImporter
+      trac-wiki = forgeimporters.trac.wiki:TracWikiImporter
       """,)


[21/50] git commit: [#5543] Require confirmation on post delete

Posted by jo...@apache.org.
[#5543] Require confirmation on post delete

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/4343ee03
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/4343ee03
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/4343ee03

Branch: refs/heads/cj/6422
Commit: 4343ee0366e682727db54bb4ac8694ef6f0ca391
Parents: 158cd46
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Jul 30 21:03:18 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Jul 31 18:49:08 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/widgets/discuss.py | 3 +++
 1 file changed, 3 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4343ee03/Allura/allura/lib/widgets/discuss.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/widgets/discuss.py b/Allura/allura/lib/widgets/discuss.py
index e62a993..eaec991 100644
--- a/Allura/allura/lib/widgets/discuss.py
+++ b/Allura/allura/lib/widgets/discuss.py
@@ -289,6 +289,9 @@ class Post(HierWidget):
                 $('.moderate_post', post).click(function(e){
                     e.preventDefault();
                     var mod = $(this).text();
+                    if (mod === 'Delete' && !confirm('Really delete this post?')) {
+                        return;
+                    }
                     var id_post = $(post).attr('id');
                     $.ajax({
                         type: 'POST',


[05/50] git commit: [#6441] ticket:398 fixed link with comments in tickets imported from trac

Posted by jo...@apache.org.
[#6441]  ticket:398 fixed link with comments  in tickets imported from trac


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

Branch: refs/heads/cj/6422
Commit: 7975c7c778e2ca346a3084513ee2fdb24ae253db
Parents: 70ee25a
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Tue Jul 23 07:19:47 2013 +0400
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 12:21:14 2013 +0000

----------------------------------------------------------------------
 ForgeTracker/forgetracker/import_support.py            | 13 ++++++++-----
 .../forgetracker/tests/functional/test_import.py       |  4 ++--
 2 files changed, 10 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/7975c7c7/ForgeTracker/forgetracker/import_support.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/import_support.py b/ForgeTracker/forgetracker/import_support.py
index 8735f40..26e182e 100644
--- a/ForgeTracker/forgetracker/import_support.py
+++ b/ForgeTracker/forgetracker/import_support.py
@@ -217,7 +217,8 @@ class ImportSupport(object):
         return '(%s)' % m.groups()[0]
 
     def ticket_bracket_link(self, m):
-        return '[#%s]' % m.groups()[0]
+        text = m.groups()[0]
+        return '[\[%s\]](%s:#%s)' % (text, c.app.config.options.mount_point, text)
 
     def get_slug_by_id(self, ticket, comment):
         comment = int(comment)
@@ -234,19 +235,21 @@ class ImportSupport(object):
             return comments.all()[comment-1].slug
 
     def comment_link(self, m):
-        ticket, comment = m.groups()
+        text, ticket, comment = m.groups()
+        ticket = ticket.replace('\n', '')
+        text = text.replace('\n', ' ')
         slug = self.get_slug_by_id(ticket, comment)
         if slug:
-            return '(%s#%s)' % (ticket, self.get_slug_by_id(ticket, comment))
+            return '[%s](%s#%s)' % (text, ticket, slug)
         else:
-            return '\(%s#comment:%s\)' % (ticket, comment)
+            return text
 
     def brackets_escaping(self, m):
         return '[\[%s\]]' % m.groups()[0]
 
     def link_processing(self, text):
         short_link_ticket_pattern = re.compile('(?<!\[)#(\d+)(?!\])')
-        comment_pattern = re.compile('\(\S*/(\d+)#comment:(\d+)\)')
+        comment_pattern = re.compile('\[(\S*\s*\S*)\]\(\S*/(\d+\n*\d*)#comment:(\d+)\)')
         ticket_pattern = re.compile('(?<=\])\(\S*ticket/(\d+)\)')
         brackets_pattern = re.compile('\[\[(.*)\]\]')
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/7975c7c7/ForgeTracker/forgetracker/tests/functional/test_import.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/test_import.py b/ForgeTracker/forgetracker/tests/functional/test_import.py
index 1fc7331..f876b93 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_import.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_import.py
@@ -177,10 +177,10 @@ class TestImportController(TestRestApiBase):
                                        #200''')
 
         assert "test link [\[2496\]](http://testlink.com)" in result
-        assert '[test comment]\(204#comment:1\)' in result
+        assert 'test comment' in result
         assert 'test link [\[2496\]](http://testlink.com)' in result
         assert 'test ticket ([#201](201))' in result
-        assert '[#200]' in result
+        assert '[\[200\]](bugs:#200)' in result, result
 
     @td.with_tracker
     def test_links(self):


[16/50] git commit: [#6446] ticket:400 fixed topics display in forum api

Posted by jo...@apache.org.
[#6446] ticket:400 fixed topics display in forum api


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

Branch: refs/heads/cj/6422
Commit: a7e25cc06604cecac7d3e3e268aae66364494bdc
Parents: 24cd410
Author: Anton Kasyanov <mi...@gmail.com>
Authored: Wed Jul 24 11:24:58 2013 +0300
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 14:55:49 2013 +0000

----------------------------------------------------------------------
 ForgeDiscussion/forgediscussion/model/forum.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a7e25cc0/ForgeDiscussion/forgediscussion/model/forum.py
----------------------------------------------------------------------
diff --git a/ForgeDiscussion/forgediscussion/model/forum.py b/ForgeDiscussion/forgediscussion/model/forum.py
index b5206c6..42d1dc0 100644
--- a/ForgeDiscussion/forgediscussion/model/forum.py
+++ b/ForgeDiscussion/forgediscussion/model/forum.py
@@ -161,8 +161,8 @@ class ForumThread(M.Thread):
 
     @property
     def status(self):
-        if self.first_post:
-            return self.first_post.status
+        if len(self.posts) == 1:
+            return self.posts[0].status
         else:
             return 'ok'
 


[29/50] git commit: [#6480] Add trac ticket importer plugin

Posted by jo...@apache.org.
[#6480] Add trac ticket importer plugin

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/512ee4b1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/512ee4b1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/512ee4b1

Branch: refs/heads/cj/6422
Commit: 512ee4b11f6fe09be909d539ff51f5b14824954c
Parents: e3663fb
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Fri Aug 2 17:00:29 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:23 2013 +0000

----------------------------------------------------------------------
 Allura/allura/scripts/trac_export.py            | 280 +++++++++++++++++++
 ForgeImporters/forgeimporters/trac/__init__.py  |  17 ++
 .../trac/templates/tickets/index.html           |  42 +++
 ForgeImporters/forgeimporters/trac/tickets.py   | 107 +++++++
 .../forgetracker/scripts/import_tracker.py      |  18 +-
 scripts/trac_export.py                          | 257 +----------------
 6 files changed, 458 insertions(+), 263 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/512ee4b1/Allura/allura/scripts/trac_export.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/trac_export.py b/Allura/allura/scripts/trac_export.py
new file mode 100644
index 0000000..aeb14ea
--- /dev/null
+++ b/Allura/allura/scripts/trac_export.py
@@ -0,0 +1,280 @@
+#!/usr/bin/env python
+
+#       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.
+
+
+import sys
+import csv
+import urlparse
+import urllib2
+import json
+import time
+import re
+from optparse import OptionParser
+from itertools import islice
+from datetime import datetime
+
+import feedparser
+from html2text import html2text
+from BeautifulSoup import BeautifulSoup, NavigableString
+import dateutil.parser
+import pytz
+
+
+def parse_options():
+    optparser = OptionParser(usage=''' %prog <Trac URL>
+
+Export ticket data from a Trac instance''')
+    optparser.add_option('-o', '--out-file', dest='out_filename', help='Write to file (default stdout)')
+    optparser.add_option('--no-attachments', dest='do_attachments', action='store_false', default=True, help='Export attachment info')
+    optparser.add_option('--only-tickets', dest='only_tickets', action='store_true', help='Export only ticket list')
+    optparser.add_option('--start', dest='start_id', type='int', default=1, help='Start with given ticket numer (or next accessible)')
+    optparser.add_option('--limit', dest='limit', type='int', default=None, help='Limit number of tickets')
+    optparser.add_option('-v', '--verbose', dest='verbose', action='store_true', help='Verbose operation')
+    options, args = optparser.parse_args()
+    if len(args) != 1:
+        optparser.error("Wrong number of arguments.")
+    return options, args
+
+
+class TracExport(object):
+
+    PAGE_SIZE = 100
+    TICKET_URL = 'ticket/%d'
+    QUERY_MAX_ID_URL  = 'query?col=id&order=id&desc=1&max=2'
+    QUERY_BY_PAGE_URL = 'query?col=id&col=time&col=changetime&order=id&max=' + str(PAGE_SIZE)+ '&page=%d'
+    ATTACHMENT_LIST_URL = 'attachment/ticket/%d/'
+    ATTACHMENT_URL = 'raw-attachment/ticket/%d/%s'
+
+    FIELD_MAP = {
+        'reporter': 'submitter',
+        'owner': 'assigned_to',
+    }
+
+    def __init__(self, base_url, start_id=1):
+        """start_id - start with at least that ticket number (actual returned
+                      ticket may have higher id if we don't have access to exact
+                      one).
+        """
+        self.base_url = base_url.rstrip('/') + '/'
+        # Contains additional info for a ticket which cannot
+        # be get with single-ticket export (create/mod times is
+        # and example).
+        self.ticket_map = {}
+        self.start_id = start_id
+        self.page = (start_id - 1) / self.PAGE_SIZE + 1
+        self.ticket_queue = self.next_ticket_ids()
+
+    def remap_fields(self, dict):
+        "Remap fields to adhere to standard taxonomy."
+        out = {}
+        for k, v in dict.iteritems():
+            out[self.FIELD_MAP.get(k, k)] = v
+
+        out['id'] = int(out['id'])
+        if 'private' in out:
+            out['private'] = bool(int(out['private']))
+        return out
+
+    def full_url(self, suburl, type=None):
+        url = urlparse.urljoin(self.base_url, suburl)
+        if type is None:
+            return url
+        glue = '&' if '?' in suburl else '?'
+        return  url + glue + 'format=' + type
+
+    @staticmethod
+    def log_url(url):
+        if options.verbose:
+            print >>sys.stderr, url
+
+    @classmethod
+    def trac2z_date(cls, s):
+        d = dateutil.parser.parse(s)
+        d = d.astimezone(pytz.UTC)
+        return d.strftime("%Y-%m-%dT%H:%M:%SZ")
+
+    @staticmethod
+    def match_pattern(regexp, string):
+        m = re.match(regexp, string)
+        assert m
+        return m.group(1)
+
+    def csvopen(self, url):
+        self.log_url(url)
+        f = urllib2.urlopen(url)
+        # Trac doesn't throw 403 error, just shows normal 200 HTML page
+        # telling that access denied. So, we'll emulate 403 ourselves.
+        # TODO: currently, any non-csv result treated as 403.
+        if not f.info()['Content-Type'].startswith('text/csv'):
+            raise urllib2.HTTPError(url, 403, 'Forbidden - emulated', f.info(), f)
+        return f
+
+    def parse_ticket_body(self, id):
+        # Use CSV export to get ticket fields
+        url = self.full_url(self.TICKET_URL % id, 'csv')
+        f = self.csvopen(url)
+        reader = csv.DictReader(f)
+        ticket_fields = reader.next()
+        ticket_fields['class'] = 'ARTIFACT'
+        return self.remap_fields(ticket_fields)
+
+    def parse_ticket_comments(self, id):
+        # Use RSS export to get ticket comments
+        url = self.full_url(self.TICKET_URL % id, 'rss')
+        self.log_url(url)
+        d = feedparser.parse(url)
+        res = []
+        for comment in d['entries']:
+            c = {}
+            c['submitter'] = comment.author
+            c['date'] = comment.updated_parsed
+            c['comment'] = html2text(comment.summary)
+            c['class'] = 'COMMENT'
+            res.append(c)
+        return res
+
+    def parse_ticket_attachments(self, id):
+        SIZE_PATTERN = r'(\d+) bytes'
+        TIMESTAMP_PATTERN = r'(.+) in Timeline'
+        # Scrape HTML to get ticket attachments
+        url = self.full_url(self.ATTACHMENT_LIST_URL % id)
+        self.log_url(url)
+        f = urllib2.urlopen(url)
+        soup = BeautifulSoup(f)
+        attach = soup.find('div', id='attachments')
+        list = []
+        while attach:
+            attach = attach.findNext('dt')
+            if not attach:
+                break
+            d = {}
+            d['filename'] = attach.a['href'].rsplit('/', 1)[1]
+            d['url'] = self.full_url(self.ATTACHMENT_URL % (id, d['filename']))
+            size_s = attach.span['title']
+            d['size'] = int(self.match_pattern(SIZE_PATTERN, size_s))
+            timestamp_s = attach.find('a', {'class': 'timeline'})['title']
+            d['date'] = self.trac2z_date(self.match_pattern(TIMESTAMP_PATTERN, timestamp_s))
+            d['by'] = attach.find(text=re.compile('added by')).nextSibling.renderContents()
+            d['description'] = ''
+            # Skip whitespace
+            while attach.nextSibling and type(attach.nextSibling) is NavigableString:
+                attach = attach.nextSibling
+            # if there's a description, there will be a <dd> element, other immediately next <dt>
+            if attach.nextSibling and attach.nextSibling.name == 'dd':
+                desc_el = attach.nextSibling
+                if desc_el:
+                    # TODO: Convert to Allura link syntax as needed
+                    d['description'] = ''.join(desc_el.findAll(text=True)).strip()
+            list.append(d)
+        return list
+
+    def get_max_ticket_id(self):
+        url = self.full_url(self.QUERY_MAX_ID_URL, 'csv')
+        f = self.csvopen(url)
+        reader = csv.DictReader(f)
+        fields = reader.next()
+        print fields
+        return int(fields['id'])
+
+    def get_ticket(self, id, extra={}):
+        '''Get ticket with given id
+        extra: extra fields to add to ticket (parsed elsewhere)
+        '''
+        t = self.parse_ticket_body(id)
+        t['comments'] = self.parse_ticket_comments(id)
+        if options.do_attachments:
+            atts = self.parse_ticket_attachments(id)
+            if atts:
+                t['attachments'] = atts
+        t.update(extra)
+        return t
+
+    def next_ticket_ids(self):
+        'Go thru ticket list and collect available ticket ids.'
+        # We could just do CSV export, which by default dumps entire list
+        # Alas, for many busy servers with long ticket list, it will just
+        # time out. So, let's paginate it instead.
+        res = []
+
+        url = self.full_url(self.QUERY_BY_PAGE_URL % self.page, 'csv')
+        try:
+            f = self.csvopen(url)
+        except urllib2.HTTPError, e:
+            if 'emulated' in e.msg:
+                body = e.fp.read()
+                if 'beyond the number of pages in the query' in body or 'Log in with a SourceForge account' in body:
+                    raise StopIteration
+            raise
+        reader = csv.reader(f)
+        cols = reader.next()
+        for r in reader:
+            if r and r[0].isdigit():
+                id = int(r[0])
+                extra = {'date': self.trac2z_date(r[1]), 'date_updated': self.trac2z_date(r[2])}
+                res.append((id, extra))
+        self.page += 1
+
+        return res
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        while True:
+            # queue empty, try to fetch more
+            if len(self.ticket_queue) == 0:
+                self.ticket_queue = self.next_ticket_ids()
+            # there aren't any more, we're really done
+            if len(self.ticket_queue) == 0:
+                raise StopIteration
+            id, extra = self.ticket_queue.pop(0)
+            if id >= self.start_id:
+                break
+        return self.get_ticket(id, extra)
+
+
+class DateJSONEncoder(json.JSONEncoder):
+    def default(self, obj):
+        if isinstance(obj, time.struct_time):
+            return time.strftime('%Y-%m-%dT%H:%M:%SZ', obj)
+        return json.JSONEncoder.default(self, obj)
+
+
+def main():
+    options, args = parse_options()
+    ex = TracExport(args[0], start_id=options.start_id)
+    # Implement iterator sequence limiting using islice()
+    doc = [t for t in islice(ex, options.limit)]
+
+    if not options.only_tickets:
+        doc = {
+            'class': 'PROJECT',
+            'trackers': {'default': {'artifacts': doc}}
+        }
+
+    out_file = sys.stdout
+    if options.out_filename:
+        out_file = open(options.out_filename, 'w')
+    out_file.write(json.dumps(doc, cls=DateJSONEncoder, indent=2, sort_keys=True))
+    # It's bad habit not to terminate lines
+    out_file.write('\n')
+
+
+if __name__ == '__main__':
+    main()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/512ee4b1/ForgeImporters/forgeimporters/trac/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/__init__.py b/ForgeImporters/forgeimporters/trac/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/__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/512ee4b1/ForgeImporters/forgeimporters/trac/templates/tickets/index.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/templates/tickets/index.html b/ForgeImporters/forgeimporters/trac/templates/tickets/index.html
new file mode 100644
index 0000000..eaf9aac
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/templates/tickets/index.html
@@ -0,0 +1,42 @@
+{#-
+       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.
+-#}
+{% extends g.theme.master %}
+
+{% block title %}
+{{c.project.name}} / Import Trac Tickets
+{% endblock %}
+
+{% block header %}
+Import tickets from Trac
+{% endblock %}
+
+{% block content %}
+<form action="create" method="post" class="pad">
+  <label for="trac_url">URL of the Trac instance</label>
+  <input name="trac_url" />
+
+  <label for="mount_label">Label</label>
+  <input name="mount_label" value="Source" />
+
+  <label for="mount_point">Mount Point</label>
+  <input name="mount_point" value="source" />
+
+  <input type="submit" />
+</form>
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/512ee4b1/ForgeImporters/forgeimporters/trac/tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
new file mode 100644
index 0000000..cc31741
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -0,0 +1,107 @@
+#       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 datetime import (
+        datetime,
+        timedelta,
+        )
+import json
+
+import formencode as fe
+from formencode import validators as fev
+
+from pylons import tmpl_context as c
+from pylons import app_globals as g
+from tg import (
+        config,
+        expose,
+        redirect,
+        validate,
+        )
+from tg.decorators import (
+        with_trailing_slash,
+        without_trailing_slash,
+        )
+
+from allura.controllers import BaseController
+from allura.lib.decorators import require_post
+from allura.lib.import_api import AlluraImportApiClient
+from allura.model import ApiTicket
+from allura.scripts.trac_export import (
+        TracExport,
+        DateJSONEncoder,
+        )
+
+from forgeimporters.base import ToolImporter
+from forgetracker.tracker_main import ForgeTrackerApp
+from forgetracker.script.import_tracker import import_tracker
+
+
+class TracTicketImportSchema(fe.Schema):
+    trac_url = fev.URL(not_empty=True)
+    mount_point = fev.UnicodeString()
+    mount_label = fev.UnicodeString()
+
+
+class TracTicketImportController(BaseController):
+    @with_trailing_slash
+    @expose('jinja:forgeimporters.trac:templates/tickets/index.html')
+    def index(self, **kw):
+        return {}
+
+    @without_trailing_slash
+    @expose()
+    @require_post()
+    @validate(TracTicketImportSchema(), error_handler=index)
+    def create(self, trac_url, mount_point, mount_label, **kw):
+        app = TracTicketImporter.import_tool(c.project,
+                mount_point=mount_point,
+                mount_label=mount_label,
+                trac_url=trac_url,
+                user=c.user)
+        redirect(app.url())
+
+
+class TracTicketImporter(ToolImporter):
+    target_app = ForgeTrackerApp
+    source = 'Trac'
+    controller = TracTicketImportController
+    tool_label = 'Trac Ticket Importer'
+    tool_description = 'Import your tickets from Trac'
+
+    def import_tool(self, project=None, mount_point=None, mount_label=None,
+            trac_url=None, user=None):
+        """ Import Trac tickets into a new Allura Tracker tool.
+
+        """
+        mount_point = mount_point or 'tickets'
+        app = project.install_app(
+                'Tickets',
+                mount_point=mount_point,
+                mount_label=mount_label or 'Tickets',
+                )
+        export = TracExport(trac_url)
+        export_string = json.dumps(export, cls=DateJSONEncoder)
+        api_ticket = ApiTicket(user_id=user._id,
+                capabilities={"import": ["Projects", project.shortname]},
+                expires=datetime.utcnow() + timedelta(minutes=60))
+        cli = AlluraImportApiClient(config['base_url'], api_ticket.api_key,
+                api_ticket.secret_key, False)
+        import_tracker(cli, project.shortname, mount_point, {},
+                export_string, validate=False)
+        g.post_event('project_updated')
+        return app

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/512ee4b1/ForgeTracker/forgetracker/scripts/import_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/scripts/import_tracker.py b/ForgeTracker/forgetracker/scripts/import_tracker.py
index 506e771..32b4d1c 100644
--- a/ForgeTracker/forgetracker/scripts/import_tracker.py
+++ b/ForgeTracker/forgetracker/scripts/import_tracker.py
@@ -25,7 +25,8 @@ from allura.lib.import_api import AlluraImportApiClient
 
 log = logging.getLogger(__name__)
 
-def import_tracker(cli, project, tool, import_options, options, doc_txt, validate=True, verbose=False):
+def import_tracker(cli, project, tool, import_options, doc_txt,
+        validate=True, verbose=False, cont=False):
     url = '/rest/p/' + project + '/' + tool
     if validate:
         url += '/validate_import'
@@ -33,8 +34,8 @@ def import_tracker(cli, project, tool, import_options, options, doc_txt, validat
         url += '/perform_import'
 
     existing_map = {}
-    if options.cont:
-        existing_tickets = cli.call('/rest/p/' + options.project + '/' + options.tracker + '/')['tickets']
+    if cont:
+        existing_tickets = cli.call('/rest/p/' + project + '/' + tool + '/')['tickets']
         for t in existing_tickets:
             existing_map[t['ticket_num']] = t['summary']
 
@@ -46,12 +47,12 @@ def import_tracker(cli, project, tool, import_options, options, doc_txt, validat
     else:
         tickets_in = doc
 
-    if options.verbose:
+    if verbose:
         print "Processing %d tickets" % len(tickets_in)
 
     for cnt, ticket_in in enumerate(tickets_in):
         if ticket_in['id'] in existing_map:
-            if options.verbose:
+            if verbose:
                 print 'Ticket id %d already exists, skipping' % ticket_in['id']
             continue
         doc_import={}
@@ -60,7 +61,7 @@ def import_tracker(cli, project, tool, import_options, options, doc_txt, validat
         doc_import['trackers']['default']['artifacts'] = [ticket_in]
         res = cli.call(url, doc=json.dumps(doc_import), options=json.dumps(import_options))
         assert res['status'] and not res['errors']
-        if options.validate:
+        if validate:
             if res['warnings']:
                 print "Ticket id %s warnings: %s" % (ticket_in['id'], res['warnings'])
         else:
@@ -93,9 +94,10 @@ class ImportTracker(ScriptTask):
         import_options['user_map'] = user_map
         cli = AlluraImportApiClient(options.base_url, options.api_key, options.secret_key, options.verbose)
         doc_txt = open(options.file_data).read()
-        import_tracker(cli, options.project, options.tracker, import_options, options, doc_txt,
+        import_tracker(cli, options.project, options.tracker, import_options, doc_txt,
                        validate=options.validate,
-                       verbose=options.verbose)
+                       verbose=options.verbose,
+                       cont=options.cont)
 
     @classmethod
     def parser(cls):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/512ee4b1/scripts/trac_export.py
----------------------------------------------------------------------
diff --git a/scripts/trac_export.py b/scripts/trac_export.py
index 002a1e8..ac90b17 100755
--- a/scripts/trac_export.py
+++ b/scripts/trac_export.py
@@ -17,259 +17,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-
-import sys
-import csv
-import urlparse
-import urllib2
-import json
-import time
-import re
-from optparse import OptionParser
-from itertools import islice
-from datetime import datetime
-
-import feedparser
-from html2text import html2text
-from BeautifulSoup import BeautifulSoup, NavigableString
-import dateutil.parser
-import pytz
-
-
-def parse_options():
-    optparser = OptionParser(usage=''' %prog <Trac URL>
-
-Export ticket data from a Trac instance''')
-    optparser.add_option('-o', '--out-file', dest='out_filename', help='Write to file (default stdout)')
-    optparser.add_option('--no-attachments', dest='do_attachments', action='store_false', default=True, help='Export attachment info')
-    optparser.add_option('--only-tickets', dest='only_tickets', action='store_true', help='Export only ticket list')
-    optparser.add_option('--start', dest='start_id', type='int', default=1, help='Start with given ticket numer (or next accessible)')
-    optparser.add_option('--limit', dest='limit', type='int', default=None, help='Limit number of tickets')
-    optparser.add_option('-v', '--verbose', dest='verbose', action='store_true', help='Verbose operation')
-    options, args = optparser.parse_args()
-    if len(args) != 1:
-        optparser.error("Wrong number of arguments.")
-    return options, args
-
-
-class TracExport(object):
-
-    PAGE_SIZE = 100
-    TICKET_URL = 'ticket/%d'
-    QUERY_MAX_ID_URL  = 'query?col=id&order=id&desc=1&max=2'
-    QUERY_BY_PAGE_URL = 'query?col=id&col=time&col=changetime&order=id&max=' + str(PAGE_SIZE)+ '&page=%d'
-    ATTACHMENT_LIST_URL = 'attachment/ticket/%d/'
-    ATTACHMENT_URL = 'raw-attachment/ticket/%d/%s'
-
-    FIELD_MAP = {
-        'reporter': 'submitter',
-        'owner': 'assigned_to',
-    }
-
-    def __init__(self, base_url, start_id=1):
-        """start_id - start with at least that ticket number (actual returned
-                      ticket may have higher id if we don't have access to exact
-                      one).
-        """
-        self.base_url = base_url.rstrip('/') + '/'
-        # Contains additional info for a ticket which cannot
-        # be get with single-ticket export (create/mod times is
-        # and example).
-        self.ticket_map = {}
-        self.start_id = start_id
-        self.page = (start_id - 1) / self.PAGE_SIZE + 1
-        self.ticket_queue = self.next_ticket_ids()
-
-    def remap_fields(self, dict):
-        "Remap fields to adhere to standard taxonomy."
-        out = {}
-        for k, v in dict.iteritems():
-            out[self.FIELD_MAP.get(k, k)] = v
-
-        out['id'] = int(out['id'])
-        if 'private' in out:
-            out['private'] = bool(int(out['private']))
-        return out
-
-    def full_url(self, suburl, type=None):
-        url = urlparse.urljoin(self.base_url, suburl)
-        if type is None:
-            return url
-        glue = '&' if '?' in suburl else '?'
-        return  url + glue + 'format=' + type
-
-    @staticmethod
-    def log_url(url):
-        if options.verbose:
-            print >>sys.stderr, url
-
-    @classmethod
-    def trac2z_date(cls, s):
-        d = dateutil.parser.parse(s)
-        d = d.astimezone(pytz.UTC)
-        return d.strftime("%Y-%m-%dT%H:%M:%SZ")
-
-    @staticmethod
-    def match_pattern(regexp, string):
-        m = re.match(regexp, string)
-        assert m
-        return m.group(1)
-
-    def csvopen(self, url):
-        self.log_url(url)
-        f = urllib2.urlopen(url)
-        # Trac doesn't throw 403 error, just shows normal 200 HTML page
-        # telling that access denied. So, we'll emulate 403 ourselves.
-        # TODO: currently, any non-csv result treated as 403.
-        if not f.info()['Content-Type'].startswith('text/csv'):
-            raise urllib2.HTTPError(url, 403, 'Forbidden - emulated', f.info(), f)
-        return f
-
-    def parse_ticket_body(self, id):
-        # Use CSV export to get ticket fields
-        url = self.full_url(self.TICKET_URL % id, 'csv')
-        f = self.csvopen(url)
-        reader = csv.DictReader(f)
-        ticket_fields = reader.next()
-        ticket_fields['class'] = 'ARTIFACT'
-        return self.remap_fields(ticket_fields)
-
-    def parse_ticket_comments(self, id):
-        # Use RSS export to get ticket comments
-        url = self.full_url(self.TICKET_URL % id, 'rss')
-        self.log_url(url)
-        d = feedparser.parse(url)
-        res = []
-        for comment in d['entries']:
-            c = {}
-            c['submitter'] = comment.author
-            c['date'] = comment.updated_parsed
-            c['comment'] = html2text(comment.summary)
-            c['class'] = 'COMMENT'
-            res.append(c)
-        return res
-
-    def parse_ticket_attachments(self, id):
-        SIZE_PATTERN = r'(\d+) bytes'
-        TIMESTAMP_PATTERN = r'(.+) in Timeline'
-        # Scrape HTML to get ticket attachments
-        url = self.full_url(self.ATTACHMENT_LIST_URL % id)
-        self.log_url(url)
-        f = urllib2.urlopen(url)
-        soup = BeautifulSoup(f)
-        attach = soup.find('div', id='attachments')
-        list = []
-        while attach:
-            attach = attach.findNext('dt')
-            if not attach:
-                break
-            d = {}
-            d['filename'] = attach.a['href'].rsplit('/', 1)[1]
-            d['url'] = self.full_url(self.ATTACHMENT_URL % (id, d['filename']))
-            size_s = attach.span['title']
-            d['size'] = int(self.match_pattern(SIZE_PATTERN, size_s))
-            timestamp_s = attach.find('a', {'class': 'timeline'})['title']
-            d['date'] = self.trac2z_date(self.match_pattern(TIMESTAMP_PATTERN, timestamp_s))
-            d['by'] = attach.find(text=re.compile('added by')).nextSibling.renderContents()
-            d['description'] = ''
-            # Skip whitespace
-            while attach.nextSibling and type(attach.nextSibling) is NavigableString:
-                attach = attach.nextSibling
-            # if there's a description, there will be a <dd> element, other immediately next <dt>
-            if attach.nextSibling and attach.nextSibling.name == 'dd':
-                desc_el = attach.nextSibling
-                if desc_el:
-                    # TODO: Convert to Allura link syntax as needed
-                    d['description'] = ''.join(desc_el.findAll(text=True)).strip()
-            list.append(d)
-        return list
-
-    def get_max_ticket_id(self):
-        url = self.full_url(self.QUERY_MAX_ID_URL, 'csv')
-        f = self.csvopen(url)
-        reader = csv.DictReader(f)
-        fields = reader.next()
-        print fields
-        return int(fields['id'])
-
-    def get_ticket(self, id, extra={}):
-        '''Get ticket with given id
-        extra: extra fields to add to ticket (parsed elsewhere)
-        '''
-        t = self.parse_ticket_body(id)
-        t['comments'] = self.parse_ticket_comments(id)
-        if options.do_attachments:
-            atts = self.parse_ticket_attachments(id)
-            if atts:
-                t['attachments'] = atts
-        t.update(extra)
-        return t
-
-    def next_ticket_ids(self):
-        'Go thru ticket list and collect available ticket ids.'
-        # We could just do CSV export, which by default dumps entire list
-        # Alas, for many busy servers with long ticket list, it will just
-        # time out. So, let's paginate it instead.
-        res = []
-
-        url = self.full_url(self.QUERY_BY_PAGE_URL % self.page, 'csv')
-        try:
-            f = self.csvopen(url)
-        except urllib2.HTTPError, e:
-            if 'emulated' in e.msg:
-                body = e.fp.read()
-                if 'beyond the number of pages in the query' in body or 'Log in with a SourceForge account' in body:
-                    raise StopIteration
-            raise
-        reader = csv.reader(f)
-        cols = reader.next()
-        for r in reader:
-            if r and r[0].isdigit():
-                id = int(r[0])
-                extra = {'date': self.trac2z_date(r[1]), 'date_updated': self.trac2z_date(r[2])}
-                res.append((id, extra))
-        self.page += 1
-
-        return res
-
-    def __iter__(self):
-        return self
-
-    def next(self):
-        while True:
-            # queue empty, try to fetch more
-            if len(self.ticket_queue) == 0:
-                self.ticket_queue = self.next_ticket_ids()
-            # there aren't any more, we're really done
-            if len(self.ticket_queue) == 0:
-                raise StopIteration
-            id, extra = self.ticket_queue.pop(0)
-            if id >= self.start_id:
-                break
-        return self.get_ticket(id, extra)
-
-
-class DateJSONEncoder(json.JSONEncoder):
-    def default(self, obj):
-        if isinstance(obj, time.struct_time):
-            return time.strftime('%Y-%m-%dT%H:%M:%SZ', obj)
-        return json.JSONEncoder.default(self, obj)
-
 if __name__ == '__main__':
-    options, args = parse_options()
-    ex = TracExport(args[0], start_id=options.start_id)
-    # Implement iterator sequence limiting using islice()
-    doc = [t for t in islice(ex, options.limit)]
-
-    if not options.only_tickets:
-        doc = {
-            'class': 'PROJECT',
-            'trackers': {'default': {'artifacts': doc}}
-        }
-
-    out_file = sys.stdout
-    if options.out_filename:
-        out_file = open(options.out_filename, 'w')
-    out_file.write(json.dumps(doc, cls=DateJSONEncoder, indent=2, sort_keys=True))
-    # It's bad habit not to terminate lines
-    out_file.write('\n')
+    from allura.scripts.trac_export import main
+    main()


[14/50] git commit: [#6446] ticket:400 removed trailing whitespaces

Posted by jo...@apache.org.
[#6446] ticket:400 removed trailing whitespaces


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

Branch: refs/heads/cj/6422
Commit: 6436aa54e610ca805edda2ef6e8287f8e5442e43
Parents: 0877d5c
Author: Anton Kasyanov <mi...@gmail.com>
Authored: Wed Jul 24 17:35:59 2013 +0300
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 14:55:49 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/discuss.py                                | 2 +-
 ForgeDiscussion/forgediscussion/tests/functional/test_rest.py | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6436aa54/Allura/allura/model/discuss.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/discuss.py b/Allura/allura/model/discuss.py
index ee62778..d13b7e9 100644
--- a/Allura/allura/model/discuss.py
+++ b/Allura/allura/model/discuss.py
@@ -336,7 +336,7 @@ class Thread(Artifact, ActivityObject):
             terms = dict(discussion_id=self.discussion_id, thread_id=self._id,
                     status={'$in': ['ok', 'pending']})
         if status:
-            terms['status'] = status       
+            terms['status'] = status
         q = self.post_class().query.find(terms)
         if style == 'threaded':
             q = q.sort('full_slug')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6436aa54/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
index 6c45a2a..afdcdc5 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
@@ -187,7 +187,7 @@ class TestRootRestController(TestDiscussionApiBase):
         assert_equal(resp.json['limit'], 1)
 
     def test_topic_show_ok_only(self):
-        thread = ForumThread.query.find({'subject': 'Hi guys'}).first()        
+        thread = ForumThread.query.find({'subject': 'Hi guys'}).first()
         url = '/rest/p/test/discussion/general/thread/%s/' % thread._id
         resp = self.app.get(url)
         posts = resp.json['topic']['posts']
@@ -198,7 +198,7 @@ class TestRootRestController(TestDiscussionApiBase):
         last_post.commit()
         ThreadLocalORMSession.flush_all()
         resp = self.app.get(url)
-        posts = resp.json['topic']['posts']        
+        posts = resp.json['topic']['posts']
         assert_equal(len(posts), 1)
 
     def test_security(self):


[42/50] git commit: Restore full taskd job error details to logging.

Posted by jo...@apache.org.
Restore full taskd job error details to logging.

This was first "fixed" in de961e2, but then in 8e3c5c3 a better fix
was made so that errormiddleware didn't get applied to tasks, thus
causing the logging to be incomplete.


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

Branch: refs/heads/cj/6422
Commit: dbffe37a59180be72a58ba31dbd2fcf60a9e1886
Parents: cb85a23
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Wed Aug 7 20:24:49 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 20:59:24 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/monq_model.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dbffe37a/Allura/allura/model/monq_model.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/monq_model.py b/Allura/allura/model/monq_model.py
index d298c1b..9fa0064 100644
--- a/Allura/allura/model/monq_model.py
+++ b/Allura/allura/model/monq_model.py
@@ -257,7 +257,7 @@ class MonQTask(MappedClass):
             self.state = 'complete'
             return self.result
         except Exception, exc:
-            log.info('Error on job %r, re-raising it', self)
+            log.exception('Error "%s" on job %s', exc, self)
             self.state = 'error'
             if hasattr(exc, 'format_error'):
                 self.result = exc.format_error()


[31/50] git commit: [#6480] Remove whitespace from shortname; use autofocus

Posted by jo...@apache.org.
[#6480] Remove whitespace from shortname; use autofocus

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/280aff07
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/280aff07
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/280aff07

Branch: refs/heads/cj/6422
Commit: 280aff074794e8d7bd2a1924a93d3f6f6fdfbb32
Parents: 63fdf19
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Mon Aug 5 19:30:50 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:23 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/templates/project.html | 2 +-
 ForgeImporters/forgeimporters/templates/project_base.html   | 4 ++--
 ForgeImporters/forgeimporters/trac/templates/project.html   | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/280aff07/ForgeImporters/forgeimporters/google/templates/project.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/templates/project.html b/ForgeImporters/forgeimporters/google/templates/project.html
index 172dcf5..a2887e7 100644
--- a/ForgeImporters/forgeimporters/google/templates/project.html
+++ b/ForgeImporters/forgeimporters/google/templates/project.html
@@ -23,7 +23,7 @@
         <label>Google Project Name</label>
     </div>
     <div class="grid-10">
-        <input id="project_name" name="project_name" value="{{c.form_values['project_name']}}"/>
+        <input id="project_name" name="project_name" value="{{c.form_values['project_name']}}" autofocus/>
         <div id="project_name_error" class="error{% if not c.form_errors['project_name'] %} hidden{% endif %}">
             {{c.form_errors['project_name']}}
         </div>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/280aff07/ForgeImporters/forgeimporters/templates/project_base.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/templates/project_base.html b/ForgeImporters/forgeimporters/templates/project_base.html
index d4db30e..6f1b683 100644
--- a/ForgeImporters/forgeimporters/templates/project_base.html
+++ b/ForgeImporters/forgeimporters/templates/project_base.html
@@ -47,7 +47,7 @@
         function suggest_name() {
             var $project_shortname = $('#project_shortname');
             if (!manual) {
-                $project_shortname.val($('#project_name').val());
+                $project_shortname.val($('#project_name').val().replace(/\s/g, '').toLowerCase());
             }
             $project_shortname.trigger('change');
         }
@@ -72,7 +72,7 @@
         }
 
         $(function() {
-            $('#project_name').focus().bind('change keyup', suggest_name);
+            $('#project_name').bind('change keyup', suggest_name);
 
             $('#project_shortname').bind('change keyup', function(event) {
                 if (event.type == 'keyup') {

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/280aff07/ForgeImporters/forgeimporters/trac/templates/project.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/templates/project.html b/ForgeImporters/forgeimporters/trac/templates/project.html
index abcfb48..869e290 100644
--- a/ForgeImporters/forgeimporters/trac/templates/project.html
+++ b/ForgeImporters/forgeimporters/trac/templates/project.html
@@ -23,7 +23,7 @@
         <label>Trac URL</label>
     </div>
     <div class="grid-10">
-        <input id="trac_url" name="trac_url" value="{{c.form_values['trac_url']}}"/>
+        <input id="trac_url" name="trac_url" value="{{c.form_values['trac_url']}}" autofocus/>
         <div id="trac_ur_errorl" class="error{% if not c.form_errors['trac_url'] %} hidden{% endif %}">
             {{c.form_errors['trac_url']}}
         </div>


[04/50] git commit: [#6441] ticket:398 fixed hyperlinks in tickets imported from trac

Posted by jo...@apache.org.
[#6441]  ticket:398 fixed hyperlinks in tickets imported from trac


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

Branch: refs/heads/cj/6422
Commit: b3fea697bf281649e6dc8cd63a878f9cb45859f3
Parents: fff526f
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Fri Jul 19 14:39:19 2013 +0400
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 12:21:13 2013 +0000

----------------------------------------------------------------------
 ForgeTracker/forgetracker/import_support.py     | 37 +++++++++++-
 .../forgetracker/tests/functional/data/sf.json  |  6 ++
 .../tests/functional/test_import.py             | 59 ++++++++++++++++++++
 3 files changed, 101 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/b3fea697/ForgeTracker/forgetracker/import_support.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/import_support.py b/ForgeTracker/forgetracker/import_support.py
index a94e70c..268f727 100644
--- a/ForgeTracker/forgetracker/import_support.py
+++ b/ForgeTracker/forgetracker/import_support.py
@@ -18,6 +18,7 @@
 #-*- python -*-
 import logging
 import json
+import re
 from datetime import datetime
 from cStringIO import StringIO
 
@@ -211,9 +212,43 @@ class ImportSupport(object):
         ticket.update(remapped)
         return ticket
 
+    def ticket_link(self, m):
+        return '(%s)' % m.groups()[0]
+
+    def get_slug_by_id(self, ticket, comment):
+        comment = int(comment)
+        ticket = TM.Ticket.query.get(app_config_id=c.app.config._id,
+                                     ticket_num=int(ticket))
+        if not ticket:
+            return ''
+        comments = ticket.discussion_thread.post_class().query.find(dict(
+            discussion_id=ticket.discussion_thread.discussion_id,
+            thread_id=ticket.discussion_thread._id,
+            status={'$in': ['ok', 'pending']})).sort('timestamp')
+
+        if comment <= comments.count():
+            return comments.all()[comment-1].slug
+
+    def comment_link(self, m):
+        ticket, comment = m.groups()
+        return '(%s#%s)' % (ticket, self.get_slug_by_id(ticket, comment))
+
+    def brackets_escaping(self, m):
+        return '[%s]' % m.groups()[0]
+
+    def link_processing(self, text):
+        comment_pattern = re.compile('\(\S*/(\d+)#comment:(\d+)\)')
+        ticket_pattern = re.compile('(?<=\])\(\S*ticket/(\d+)\)')
+        brackets_pattern = re.compile('\[\[(.*)\]\]')
+
+        text = comment_pattern.sub(self.comment_link, text.replace('\n', ''))
+        text = ticket_pattern.sub(self.ticket_link, text)
+        text = brackets_pattern.sub(self.brackets_escaping, text)
+        return text
+
     def make_comment(self, thread, comment_dict):
         ts = self.parse_date(comment_dict['date'])
-        comment = thread.post(text=comment_dict['comment'], timestamp=ts)
+        comment = thread.post(text=self.link_processing(comment_dict['comment']), timestamp=ts)
         comment.author_id = self.get_user_id(comment_dict['submitter'])
         comment.import_id = c.api_token.api_key
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/b3fea697/ForgeTracker/forgetracker/tests/functional/data/sf.json
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/data/sf.json b/ForgeTracker/forgetracker/tests/functional/data/sf.json
index 58e322c..5a11379 100644
--- a/ForgeTracker/forgetracker/tests/functional/data/sf.json
+++ b/ForgeTracker/forgetracker/tests/functional/data/sf.json
@@ -41,6 +41,12 @@
               "comment": "  * **status** changed from _accepted_ to _closed_\n\n  * **resolution** set to _fixed_\n\nHello,\n\nThis issue is should be resolved with the site redesign.\n\nRegards, Chris Tsai, SourceForge.net Support\n\n", 
               "date": "2009-07-20T15:44:32Z", 
               "submitter": "ctsai"
+            },
+            {
+              "class": "COMMENT",
+              "comment": "test link [[2496]](http://testlink.com)  test ticket ([#201](http://sourceforge.net/apps/trac/sourceforge/ticket/201)) \n [test comment](http://sourceforge.net/apps/trac/sourceforge/ticket/204#comment:1)",
+              "date": "2009-07-21T15:44:32Z",
+              "submitter": "ctsai"
             }
           ], 
           "date": "2009-04-13T08:49:13Z", 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/b3fea697/ForgeTracker/forgetracker/tests/functional/test_import.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/test_import.py b/ForgeTracker/forgetracker/tests/functional/test_import.py
index 06fde25..202c757 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_import.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_import.py
@@ -24,10 +24,13 @@ from nose.tools import assert_equal
 
 import ming
 from pylons import app_globals as g
+from pylons import tmpl_context as c
 
 from allura import model as M
 from alluratest.controller import TestRestApiBase
 from allura.tests import decorators as td
+from forgetracker import model as TM
+from forgetracker.import_support import ImportSupport
 
 class TestImportController(TestRestApiBase):
 
@@ -164,3 +167,59 @@ class TestImportController(TestRestApiBase):
         assert ticket_json['summary'] in r
         r = self.app.get('/p/test/bugs/')
         assert ticket_json['summary'] in r
+
+    @td.with_tracker
+    def test_link_processing(self):
+        import_support = ImportSupport()
+        result = import_support.link_processing('''test link [[2496]](http://testlink.com)
+                                       test ticket ([#201](http://sourceforge.net/apps/trac/sourceforge/ticket/201))
+                                       [test comment](http://sourceforge.net/apps/trac/sourceforge/ticket/204#comment:1)''')
+
+        assert "test link [2496](http://testlink.com)" in result
+        assert '[test comment](204#)' in result
+        assert 'test link [2496](http://testlink.com)' in result
+        assert 'test ticket ([#201](201))' in result
+
+    @td.with_tracker
+    def test_links(self):
+        api_ticket = M.ApiTicket(user_id=self.user._id, capabilities={'import': ['Projects','test']},
+                                 expires=datetime.utcnow() + timedelta(days=1))
+        ming.orm.session(api_ticket).flush()
+        self.set_api_token(api_ticket)
+
+        doc_text = open(os.path.dirname(__file__) + '/data/sf.json').read()
+        self.api_post('/rest/p/test/bugs/perform_import',
+                      doc=doc_text, options='{"user_map": {"hinojosa4": "test-admin", "ma_boehm": "test-user"}}')
+
+        r = self.app.get('/p/test/bugs/204/')
+        ticket = TM.Ticket.query.get(app_config_id=c.app.config._id,
+                                    ticket_num=204)
+        slug = ticket.discussion_thread.post_class().query.find(dict(
+            discussion_id=ticket.discussion_thread.discussion_id,
+            thread_id=ticket.discussion_thread._id,
+            status={'$in': ['ok', 'pending']})).sort('timestamp').all()[0].slug
+
+        assert '[test comment](204#%s)' % slug in r
+        assert 'test link [2496](http://testlink.com)' in r
+        assert 'test ticket ([#201](201))' in r
+
+    @td.with_tracker
+    def test_slug(self):
+        api_ticket = M.ApiTicket(user_id=self.user._id, capabilities={'import': ['Projects','test']},
+                                 expires=datetime.utcnow() + timedelta(days=1))
+        ming.orm.session(api_ticket).flush()
+        self.set_api_token(api_ticket)
+
+        doc_text = open(os.path.dirname(__file__) + '/data/sf.json').read()
+        self.api_post('/rest/p/test/bugs/perform_import',
+                      doc=doc_text, options='{"user_map": {"hinojosa4": "test-admin", "ma_boehm": "test-user"}}')
+        ticket = TM.Ticket.query.get(app_config_id=c.app.config._id,
+                                    ticket_num=204)
+        comments = ticket.discussion_thread.post_class().query.find(dict(
+            discussion_id=ticket.discussion_thread.discussion_id,
+            thread_id=ticket.discussion_thread._id,
+            status={'$in': ['ok', 'pending']})).sort('timestamp').all()
+
+        import_support = ImportSupport()
+        assert_equal(import_support.get_slug_by_id('204', '1'), comments[0].slug)
+        assert_equal(import_support.get_slug_by_id('204', '2'), comments[1].slug)


[44/50] git commit: [#4665] ticket:401 added source_branch to MergeRequest

Posted by jo...@apache.org.
[#4665] ticket:401 added source_branch to MergeRequest


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

Branch: refs/heads/cj/6422
Commit: 439222c6079a3e56b2551316a84f1872d5397f53
Parents: 2b78927
Author: Anton Kasyanov <mi...@gmail.com>
Authored: Wed Jul 31 12:59:16 2013 +0300
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 7 22:35:27 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/repository.py |  1 +
 Allura/allura/model/repository.py       |  1 +
 ForgeGit/forgegit/model/git_repo.py     | 15 +++++++++++++--
 3 files changed, 15 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/439222c6/Allura/allura/controllers/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/repository.py b/Allura/allura/controllers/repository.py
index d9331c7..aaf9f6a 100644
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.py
@@ -178,6 +178,7 @@ class RepoRootController(BaseController, FeedController):
             mr = M.MergeRequest.upsert(
                 downstream=downstream,
                 target_branch=kw['target_branch'],
+                source_branch=kw['source_branch'],
                 summary=kw['summary'],
                 description=kw['description'])
             M.Notification.post(

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/439222c6/Allura/allura/model/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index a3d9a44..e29cd0a 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -557,6 +557,7 @@ class MergeRequest(VersionedArtifact, ActivityObject):
             project_id=S.ObjectId,
             mount_point=str,
             commit_id=str))
+    source_branch=FieldProperty(str,if_missing='')
     target_branch=FieldProperty(str)
     creator_id=FieldProperty(S.ObjectId, if_missing=lambda:c.user._id)
     created=FieldProperty(datetime, if_missing=datetime.utcnow)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/439222c6/ForgeGit/forgegit/model/git_repo.py
----------------------------------------------------------------------
diff --git a/ForgeGit/forgegit/model/git_repo.py b/ForgeGit/forgegit/model/git_repo.py
index 9906c0b..a982b2b 100644
--- a/ForgeGit/forgegit/model/git_repo.py
+++ b/ForgeGit/forgegit/model/git_repo.py
@@ -78,9 +78,20 @@ class Repository(M.Repository):
 
     def merge_command(self, merge_request):
         '''Return the command to merge a given commit to a given target branch'''
-        return 'git checkout %s\ngit fetch %s\ngit merge %s' % (
+        if merge_request.source_branch:
+            fetch_command = 'git fetch {} {}'.format(
+                merge_request.downstream_repo_url,
+                merge_request.source_branch,
+            )
+        else:
+            fetch_command = 'git fetch {} {} # warning: '\
+            'no source branch specified'.format(
+                merge_request.downstream_repo_url,
+                '<source_branch>',
+            )
+        return 'git checkout %s\n%s\ngit merge %s' % (
             merge_request.target_branch,
-            merge_request.downstream_repo_url,
+            fetch_command,
             merge_request.downstream.commit_id,
         )
 


[39/50] git commit: [#6480] Fix tests to match refactors

Posted by jo...@apache.org.
[#6480] Fix tests to match refactors

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/1d5708d0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/1d5708d0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/1d5708d0

Branch: refs/heads/cj/6422
Commit: 1d5708d07dbac80cad787c99c5d4595559c52cd6
Parents: f398a06
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 19:38:52 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/tests/google/test_tasks.py         |  9 ---------
 ForgeImporters/forgeimporters/tests/test_base.py      | 14 +++++++++++++-
 2 files changed, 13 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/1d5708d0/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 23da83f..dc7d936 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tasks.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tasks.py
@@ -31,12 +31,3 @@ def test_import_project_info(c, session, gpe):
     gpe.return_value.get_icon.assert_called_once_with()
     gpe.return_value.get_license.assert_called_once_with()
     session.flush_all.assert_called_once_with()
-
-
-@mock.patch.object(tasks.ToolImporter, 'by_name')
-@mock.patch.object(tasks, 'c')
-def test_import_tool(c, by_name):
-    c.project = mock.Mock(name='project')
-    tasks.import_tool('importer_name', 'project_name', 'mount_point', 'mount_label')
-    by_name.assert_called_once_with('importer_name')
-    by_name.return_value.import_tool.assert_called_once_with(c.project, 'project_name', 'mount_point', 'mount_label')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/1d5708d0/ForgeImporters/forgeimporters/tests/test_base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/test_base.py b/ForgeImporters/forgeimporters/tests/test_base.py
index 1558db4..18b83ac 100644
--- a/ForgeImporters/forgeimporters/tests/test_base.py
+++ b/ForgeImporters/forgeimporters/tests/test_base.py
@@ -23,6 +23,18 @@ import mock
 from .. import base
 
 
+@mock.patch.object(base.ToolImporter, 'by_name')
+@mock.patch.object(base, 'c')
+def test_import_tool(c, by_name):
+    c.project = mock.Mock(name='project')
+    c.user = mock.Mock(name='user')
+    base.import_tool('importer_name', 'project_name', 'mount_point', 'mount_label')
+    by_name.assert_called_once_with('importer_name')
+    by_name.return_value.import_tool.assert_called_once_with(c.project,
+            'project_name', user=c.user, mount_point='mount_point',
+            mount_label='mount_label')
+
+
 def ep(name, source=None, importer=None, **kw):
     mep = mock.Mock(name='mock_ep', **kw)
     mep.name = name
@@ -39,7 +51,7 @@ class TestProjectImporter(TestCase):
     @mock.patch.object(base, 'iter_entry_points')
     def test_tool_importers(self, iep):
         eps = iep.return_value = [ep('ep1', 'foo'), ep('ep2', 'bar'), ep('ep3', 'foo')]
-        pi = base.ProjectImporter()
+        pi = base.ProjectImporter(mock.Mock(name='neighborhood'))
         pi.source = 'foo'
         self.assertEqual(pi.tool_importers, {'ep1': eps[0].lv, 'ep3': eps[2].lv})
         iep.assert_called_once_with('allura.importers')


[28/50] git commit: [#6461] fix pyflakes issues

Posted by jo...@apache.org.
[#6461] fix pyflakes issues


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

Branch: refs/heads/cj/6422
Commit: e3663fb938d6c63d82dac151b429e90e1effb500
Parents: fb06265
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Tue Aug 6 22:11:15 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Tue Aug 6 22:11:15 2013 +0000

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


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e3663fb9/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 4bc602c..4d37a16 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -20,6 +20,7 @@ from datetime import datetime
 
 from pylons import tmpl_context as c
 #import gdata
+gdata = None
 from ming.orm import session
 
 from allura.lib import helpers as h
@@ -156,8 +157,8 @@ class GDataAPIExtractor(object):
         client = gdata.projecthosting.client.ProjectHostingClient()
         while True:
             query = gdata.projecthosting.client.Query(start_index=start, max_results=limit)
-            issues = client.get_comments(self.project_name, query=query).entry
-            if len(issues) <= 0:
+            comments = client.get_comments(self.project_name, query=query).entry
+            if len(comments) <= 0:
                 return
             for comment in comments:
                 yield GDataAPIComment(comment)


[46/50] git commit: [#4665] Provide working (if inefficient) git merge commands when missing source_branch

Posted by jo...@apache.org.
[#4665] Provide working (if inefficient) git merge commands when missing source_branch

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/6711c10a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/6711c10a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/6711c10a

Branch: refs/heads/cj/6422
Commit: 6711c10a2e55ab3edc31bf14ab511e9eaa7dc746
Parents: 37b8fff
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Wed Aug 7 22:34:45 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed Aug 7 22:35:27 2013 +0000

----------------------------------------------------------------------
 ForgeGit/forgegit/model/git_repo.py | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6711c10a/ForgeGit/forgegit/model/git_repo.py
----------------------------------------------------------------------
diff --git a/ForgeGit/forgegit/model/git_repo.py b/ForgeGit/forgegit/model/git_repo.py
index a982b2b..e0e47b8 100644
--- a/ForgeGit/forgegit/model/git_repo.py
+++ b/ForgeGit/forgegit/model/git_repo.py
@@ -84,11 +84,12 @@ class Repository(M.Repository):
                 merge_request.source_branch,
             )
         else:
-            fetch_command = 'git fetch {} {} # warning: '\
-            'no source branch specified'.format(
-                merge_request.downstream_repo_url,
-                '<source_branch>',
-            )
+            fetch_command = (
+                    'git remote add merge_request {}\n'
+                    'git fetch merge_request'
+                ).format(
+                    merge_request.downstream_repo_url,
+                )
         return 'git checkout %s\n%s\ngit merge %s' % (
             merge_request.target_branch,
             fetch_command,


[34/50] git commit: [#6480] Add tests for Trac wiki importer

Posted by jo...@apache.org.
[#6480] Add tests for Trac wiki importer

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/2379488a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/2379488a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/2379488a

Branch: refs/heads/cj/6422
Commit: 2379488ac978b40c027f28720c92a97d9d54a585
Parents: 8a365f7
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 21:59:58 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/trac/tests/test_tickets.py   |  1 +
 .../forgeimporters/trac/tests/test_wiki.py      | 88 ++++++++++++++++++++
 ForgeImporters/forgeimporters/trac/tickets.py   |  2 +-
 ForgeImporters/forgeimporters/trac/wiki.py      |  2 +-
 4 files changed, 91 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2379488a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
index 8b102a9..2cce886 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -84,6 +84,7 @@ class TestTracTicketImportController(TestController, TestCase):
     @patch('forgeimporters.trac.tickets.TracTicketImporter')
     def test_create(self, importer):
         from allura import model as M
+        importer = importer.return_value
         importer.import_tool.return_value = Mock()
         importer.import_tool.return_value.url.return_value = '/p/test/mymount'
         params = dict(trac_url='http://example.com/trac/url',

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2379488a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
index 77505f1..71cc8b5 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
@@ -15,3 +15,91 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from unittest import TestCase
+from mock import Mock, patch
+
+from allura.tests import TestController
+from allura.tests.decorators import with_wiki
+
+from forgeimporters.trac.wiki import (
+    TracWikiImporter,
+    TracWikiImportController,
+    )
+
+
+class TestWikiTicketImporter(TestCase):
+    @patch('forgeimporters.trac.wiki.session')
+    @patch('forgeimporters.trac.wiki.tempfile.NamedTemporaryFile')
+    @patch('forgeimporters.trac.wiki.g')
+    @patch('forgeimporters.trac.wiki.WikiFromTrac')
+    @patch('forgeimporters.trac.wiki.load_data')
+    @patch('forgeimporters.trac.wiki.argparse.Namespace')
+    @patch('forgeimporters.trac.wiki.WikiExporter')
+    @patch('forgeimporters.trac.wiki.ApiTicket')
+    @patch('forgeimporters.trac.wiki.datetime')
+    def test_import_tool(self, dt, ApiTicket, WikiExporter, Namespace,
+            load_data, WikiFromTrac, g, NamedTemporaryFile, session):
+        from datetime import datetime, timedelta
+        now = datetime.utcnow()
+        dt.utcnow.return_value = now
+        export_file = NamedTemporaryFile.return_value.__enter__.return_value
+        export_file.name = '/my/file'
+
+        importer = TracWikiImporter()
+        app = Mock(name='ForgeWikiApp')
+        project = Mock(name='Project', shortname='myproject')
+        project.install_app.return_value = app
+        user = Mock(name='User', _id='id')
+        res = importer.import_tool(project=project,
+                mount_point='pages',
+                mount_label='Pages',
+                trac_url='http://example.com/trac/url',
+                user=user)
+        self.assertEqual(res, app)
+        project.install_app.assert_called_once_with(
+                'Wiki', mount_point='pages', mount_label='Pages')
+        ApiTicket.assert_called_once_with(
+                user_id=user._id,
+                capabilities={"import": ["Projects", "myproject"]},
+                expires=now + timedelta(minutes=60))
+        WikiExporter.assert_called_once_with('http://example.com/trac/url/',
+                Namespace.return_value)
+        WikiExporter.return_value.export.assert_called_once_with(export_file)
+        load_data.assert_called_once_with('/my/file',
+                WikiFromTrac.parser.return_value, Namespace.return_value)
+        g.post_event.assert_called_once_with('project_updated')
+
+
+class TestTracWikiImportController(TestController, TestCase):
+    def setUp(self):
+        """Mount Trac import controller on the Wiki admin controller"""
+        super(self.__class__, self).setUp()
+        from forgewiki.wiki_main import WikiAdminController
+        WikiAdminController._importer = TracWikiImportController()
+
+    @with_wiki
+    def test_index(self):
+        r = self.app.get('/p/test/admin/wiki/_importer/')
+        self.assertIsNotNone(r.html.find(attrs=dict(name="trac_url")))
+        self.assertIsNotNone(r.html.find(attrs=dict(name="mount_label")))
+        self.assertIsNotNone(r.html.find(attrs=dict(name="mount_point")))
+
+    @with_wiki
+    @patch('forgeimporters.trac.wiki.TracWikiImporter')
+    def test_create(self, importer):
+        from allura import model as M
+        importer = importer.return_value
+        importer.import_tool.return_value = Mock()
+        importer.import_tool.return_value.url.return_value = '/p/test/mymount'
+        params = dict(trac_url='http://example.com/trac/url',
+                mount_label='mylabel',
+                mount_point='mymount',
+                )
+        r = self.app.post('/p/test/admin/wiki/_importer/create', params,
+                status=302)
+        project = M.Project.query.get(shortname='test')
+        self.assertEqual(r.location, 'http://localhost/p/test/mymount')
+        self.assertEqual(project._id, importer.import_tool.call_args[0][0]._id)
+        self.assertEqual(u'mymount', importer.import_tool.call_args[1]['mount_point'])
+        self.assertEqual(u'mylabel', importer.import_tool.call_args[1]['mount_label'])
+        self.assertEqual(u'http://example.com/trac/url', importer.import_tool.call_args[1]['trac_url'])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2379488a/ForgeImporters/forgeimporters/trac/tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index 0f13649..78d8d17 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -69,7 +69,7 @@ class TracTicketImportController(BaseController):
     @require_post()
     @validate(TracTicketImportSchema(), error_handler=index)
     def create(self, trac_url, mount_point, mount_label, **kw):
-        app = TracTicketImporter.import_tool(c.project,
+        app = TracTicketImporter().import_tool(c.project,
                 mount_point=mount_point,
                 mount_label=mount_label,
                 trac_url=trac_url,

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2379488a/ForgeImporters/forgeimporters/trac/wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/wiki.py b/ForgeImporters/forgeimporters/trac/wiki.py
index e7ead86..00f0d48 100644
--- a/ForgeImporters/forgeimporters/trac/wiki.py
+++ b/ForgeImporters/forgeimporters/trac/wiki.py
@@ -68,7 +68,7 @@ class TracWikiImportController(BaseController):
     @require_post()
     @validate(TracWikiImportSchema(), error_handler=index)
     def create(self, trac_url, mount_point, mount_label, **kw):
-        app = TracWikiImporter.import_tool(c.project,
+        app = TracWikiImporter().import_tool(c.project,
                 mount_point=mount_point,
                 mount_label=mount_label,
                 trac_url=trac_url,


[18/50] git commit: [#6456] Refactored ProjectImporterDispatcher to Allura to remove dependency on FI

Posted by jo...@apache.org.
[#6456] Refactored ProjectImporterDispatcher to Allura to remove dependency on FI

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/4ed7af81
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/4ed7af81
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/4ed7af81

Branch: refs/heads/cj/6422
Commit: 4ed7af81a3f595f4081f966f7d2547671e73f8b5
Parents: 868694e
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue Jul 23 18:26:48 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Jul 31 18:44:34 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/project.py  | 14 ++++++++++++--
 ForgeImporters/forgeimporters/base.py | 15 ++++-----------
 2 files changed, 16 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4ed7af81/Allura/allura/controllers/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index 8af4396..d2b8510 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -20,6 +20,7 @@ import logging
 from datetime import datetime, timedelta
 from urllib import unquote
 from itertools import chain, islice
+from pkg_resources import iter_entry_points
 
 from bson import ObjectId
 from tg import expose, flash, redirect, validate, request, response, config
@@ -47,7 +48,6 @@ from allura.lib.widgets import forms as ff
 from allura.lib.widgets import form_fields as ffw
 from allura.lib.widgets import project_list as plw
 from allura.lib import plugin, exceptions
-from forgeimporters.base import ProjectImporterDispatcher
 from .auth import AuthController
 from .search import SearchController, ProjectBrowseController
 from .static import NewForgeController
@@ -74,7 +74,7 @@ class NeighborhoodController(object):
         self.browse = NeighborhoodProjectBrowseController(neighborhood=self.neighborhood)
         self._admin = NeighborhoodAdminController(self.neighborhood)
         self._moderate = NeighborhoodModerateController(self.neighborhood)
-        self.import_project = ProjectImporterDispatcher(self.neighborhood)
+        self.import_project = ProjectImporterController(self.neighborhood)
 
     def _check_security(self):
         require_access(self.neighborhood, 'read')
@@ -919,3 +919,13 @@ class GrantController(object):
         with h.push_context(self.project._id):
             g.post_event('project_updated')
         redirect(request.referer)
+
+class ProjectImporterController(object):
+    def __init__(self, neighborhood, *a, **kw):
+        super(ProjectImporterController, self).__init__(*a, **kw)
+        self.neighborhood = neighborhood
+
+    @expose()
+    def _lookup(self, source, *rest):
+        for ep in iter_entry_points('allura.project_importers', source):
+            return ep.load()(self.neighborhood), rest

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/4ed7af81/ForgeImporters/forgeimporters/base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index 777eb8a..bf22f8a 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -25,18 +25,9 @@ from ming.utils import LazyProperty
 from allura.controllers import BaseController
 
 
-class ProjectImporterDispatcher(BaseController):
-    def __init__(self, neighborhood, *a, **kw):
-        super(ProjectImporterDispatcher, self).__init__(*a, **kw)
-        self.neighborhood = neighborhood
-
-    @expose()
-    def _lookup(self, source, *rest):
-        for ep in iter_entry_points('allura.project_importers', source):
-            return ep.load()(self.neighborhood), rest
-
-
 class ProjectImporter(BaseController):
+    """
+    """
     source = None
 
     @LazyProperty
@@ -73,6 +64,8 @@ class ProjectImporter(BaseController):
 
 
 class ToolImporter(object):
+    """
+    """
     target_app = None  # app or list of apps
     source = None  # string description of source, must match project importer
     controller = None


[19/50] git commit: [#6456] Add project_update event to GC project and code importer

Posted by jo...@apache.org.
[#6456] Add project_update event to GC project and 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/158cd46b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/158cd46b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/158cd46b

Branch: refs/heads/cj/6422
Commit: 158cd46bcd01c271baa11569361583c0408b56cd
Parents: 227396f
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Thu Jul 25 15:10:42 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Jul 31 18:44:35 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/code.py     |  5 ++++-
 ForgeImporters/forgeimporters/google/tasks.py    |  2 ++
 ForgeImporters/forgeimporters/tests/test_base.py | 11 -----------
 3 files changed, 6 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/158cd46b/ForgeImporters/forgeimporters/google/code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/code.py b/ForgeImporters/forgeimporters/google/code.py
index 1ce51de..8e047fb 100644
--- a/ForgeImporters/forgeimporters/google/code.py
+++ b/ForgeImporters/forgeimporters/google/code.py
@@ -19,6 +19,7 @@ import formencode as fe
 from formencode import validators as fev
 
 from pylons import tmpl_context as c
+from pylons import app_globals as g
 from tg import (
         expose,
         redirect,
@@ -111,9 +112,11 @@ class GoogleRepoImporter(ToolImporter):
         repo_type = extractor.get_repo_type()
         repo_url = get_repo_url(project.get_tool_data('google-code',
             'project_name'), repo_type)
-        return project.install_app(
+        app = project.install_app(
                 REPO_ENTRY_POINTS[repo_type],
                 mount_point=mount_point or 'code',
                 mount_label=mount_label or 'Code',
                 init_from_url=repo_url,
                 )
+        g.post_event('project_updated')
+        return app

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/158cd46b/ForgeImporters/forgeimporters/google/tasks.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tasks.py b/ForgeImporters/forgeimporters/google/tasks.py
index 834dc9d..65dd126 100644
--- a/ForgeImporters/forgeimporters/google/tasks.py
+++ b/ForgeImporters/forgeimporters/google/tasks.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 from pylons import tmpl_context as c
+from pylons import app_globals as g
 
 from ming.orm import ThreadLocalORMSession
 
@@ -32,6 +33,7 @@ def import_project_info():
     extractor.get_icon()
     extractor.get_license()
     ThreadLocalORMSession.flush_all()
+    g.post_event('project_updated')
 
 @task
 def import_tool(importer_name, mount_point=None, mount_label=None):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/158cd46b/ForgeImporters/forgeimporters/tests/test_base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/test_base.py b/ForgeImporters/forgeimporters/tests/test_base.py
index 303570d..1558db4 100644
--- a/ForgeImporters/forgeimporters/tests/test_base.py
+++ b/ForgeImporters/forgeimporters/tests/test_base.py
@@ -35,17 +35,6 @@ def ep(name, source=None, importer=None, **kw):
     return mep
 
 
-class TestProjectImporterDispatcher(TestCase):
-    @mock.patch.object(base, 'iter_entry_points')
-    def test_lookup(self, iep):
-        eps = iep.return_value = [ep('ep1', 'first'), ep('ep2', 'second')]
-        nbhd = mock.Mock(name='neighborhood')
-        result = base.ProjectImporterDispatcher(nbhd)._lookup('source', 'rest1', 'rest2')
-        self.assertEqual(result, (eps[0].lv, ('rest1', 'rest2')))
-        iep.assert_called_once_with('allura.project_importers', 'source')
-        eps[0].load.return_value.assert_called_once_with(nbhd)
-
-
 class TestProjectImporter(TestCase):
     @mock.patch.object(base, 'iter_entry_points')
     def test_tool_importers(self, iep):


[12/50] git commit: [#6446] ticket:400 added filtering for topics in api, test fail

Posted by jo...@apache.org.
[#6446] ticket:400 added filtering for topics in api, test fail


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

Branch: refs/heads/cj/6422
Commit: dbb1cb191586af7293303208115a83efb1e07829
Parents: 625bf2e
Author: Anton Kasyanov <mi...@gmail.com>
Authored: Mon Jul 22 14:09:15 2013 +0300
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Wed Jul 31 14:55:48 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/discuss.py                  |  4 +++-
 .../forgediscussion/controllers/root.py         |  2 +-
 .../tests/functional/test_rest.py               | 21 ++++++++++++++++++++
 3 files changed, 25 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dbb1cb19/Allura/allura/model/discuss.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/discuss.py b/Allura/allura/model/discuss.py
index 76cf615..97d19d0 100644
--- a/Allura/allura/model/discuss.py
+++ b/Allura/allura/model/discuss.py
@@ -328,13 +328,15 @@ class Thread(Artifact, ActivityObject):
         return result
 
     def query_posts(self, page=None, limit=None,
-                    timestamp=None, style='threaded'):
+                    timestamp=None, style='threaded', status=None):
         if timestamp:
             terms = dict(discussion_id=self.discussion_id, thread_id=self._id,
                     status={'$in': ['ok', 'pending']}, timestamp=timestamp)
         else:
             terms = dict(discussion_id=self.discussion_id, thread_id=self._id,
                     status={'$in': ['ok', 'pending']})
+        if status:
+            terms['status'] = status       
         q = self.post_class().query.find(terms)
         if style == 'threaded':
             q = q.sort('full_slug')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dbb1cb19/ForgeDiscussion/forgediscussion/controllers/root.py
----------------------------------------------------------------------
diff --git a/ForgeDiscussion/forgediscussion/controllers/root.py b/ForgeDiscussion/forgediscussion/controllers/root.py
index 67e51ca..7dd18c7 100644
--- a/ForgeDiscussion/forgediscussion/controllers/root.py
+++ b/ForgeDiscussion/forgediscussion/controllers/root.py
@@ -334,7 +334,7 @@ class ForumTopicRestController(BaseController):
     @expose('json:')
     def index(self, limit=100, page=0, **kw):
         limit, page, start = g.handle_paging(int(limit), int(page))
-        posts = self.topic.query_posts(page=page, limit=limit, style='')
+        posts = self.topic.query_posts(page=page, limit=limit, style='', status='ok')
         json = {}
         json['topic'] = self.topic.__json__()
         json['count'] = posts.count()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dbb1cb19/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
index 56c01ff..7fc3499 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
@@ -168,6 +168,27 @@ class TestRootRestController(TestDiscussionApiBase):
         assert_equal(resp.json['page'], 1)
         assert_equal(resp.json['limit'], 1)
 
+    def test_topic_show_ok_only(self):
+        # import logging
+        # log = logging.getLogger(__name__)
+
+        thread = ForumThread.query.find({'subject': 'Hi guys'}).first()        
+        url = '/rest/p/test/discussion/general/thread/%s/' % thread._id
+        resp = self.app.get(url)
+        posts = resp.json['topic']['posts']
+        assert_equal(len(posts), 1)
+        thread.post('Hello', 'I am not ok post')
+        last_post = thread.last_post
+        last_post.status = 'pending'
+        last_post.commit()
+
+        resp = self.app.get(url)
+        posts = resp.json['topic']['posts']        
+
+        # log.info('ready to debug')
+        # log.info(posts)
+        assert_equal(len(posts), 1)
+
     def test_security(self):
         p = M.Project.query.get(shortname='test')
         acl = p.app_instance('discussion').config.acl


[03/50] git commit: [#6504] Refactored all project name validation into validator class

Posted by jo...@apache.org.
[#6504] Refactored all project name validation into validator class

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/fff526fe
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/fff526fe
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/fff526fe

Branch: refs/heads/cj/6422
Commit: fff526fe7cbb03cacc941872731f61f0dd84225e
Parents: be9d822
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue Jul 30 18:21:34 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Tue Jul 30 20:12:00 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/project.py |  5 +--
 Allura/allura/lib/exceptions.py      |  5 ++-
 Allura/allura/lib/plugin.py          | 52 +++++--------------------------
 Allura/allura/lib/widgets/forms.py   | 39 ++++++++++++++++++-----
 Allura/allura/tests/test_plugin.py   | 46 ++++++++++-----------------
 5 files changed, 62 insertions(+), 85 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/fff526fe/Allura/allura/controllers/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index 91d5a4f..8af4396 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -83,8 +83,9 @@ class NeighborhoodController(object):
     def _lookup(self, pname, *remainder):
         pname = unquote(pname)
         provider = plugin.ProjectRegistrationProvider.get()
-        valid, reason = provider.valid_project_shortname(pname, self.neighborhood)
-        if not valid:
+        try:
+            provider.shortname_validator.to_python(pname, check_allowed=False, neighborhood=self.neighborhood)
+        except Invalid as e:
             raise exc.HTTPNotFound, pname
         project = M.Project.query.get(shortname=self.prefix + pname, neighborhood_id=self.neighborhood._id)
         if project is None and self.prefix == 'u/':

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/fff526fe/Allura/allura/lib/exceptions.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/exceptions.py b/Allura/allura/lib/exceptions.py
index e222cf5..4c33991 100644
--- a/Allura/allura/lib/exceptions.py
+++ b/Allura/allura/lib/exceptions.py
@@ -15,8 +15,11 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from formencode import Invalid
+
 class ForgeError(Exception): pass
-class ProjectConflict(ForgeError): pass
+class ProjectConflict(ForgeError, Invalid): pass
+class ProjectShortnameInvalid(ForgeError, Invalid): pass
 class ProjectOverlimitError(ForgeError): pass
 class ProjectRatelimitError(ForgeError): pass
 class ToolError(ForgeError): pass

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/fff526fe/Allura/allura/lib/plugin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index 7626bdb..4afd9fc 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -352,11 +352,18 @@ class ProjectRegistrationProvider(object):
         myprovider = foo.bar:MyAuthProvider
 
     Then in your .ini file, set registration.method=myprovider
+
+    The provider should expose an attribute, `shortname_validator` which is
+    an instance of a FormEncode validator that validates project shortnames.
+    The `to_python()` method of the validator should accept a `check_allowed`
+    argument to indicate whether additional checks beyond correctness of the
+    name should be done, such as whether the name is already in use.
     '''
 
     def __init__(self):
         from allura.lib.widgets import forms
         self.add_project_widget = forms.NeighborhoodAddProjectForm
+        self.shortname_validator = forms.NeighborhoodProjectShortNameValidator()
 
     @classmethod
     def get(cls):
@@ -364,15 +371,6 @@ class ProjectRegistrationProvider(object):
         method = config.get('registration.method', 'local')
         return app_globals.Globals().entry_points['registration'][method]()
 
-    def _name_taken(self, project_name, neighborhood):
-        """Return False if ``project_name`` is available in ``neighborhood``.
-        If unavailable, return an error message (str) explaining why.
-
-        """
-        from allura import model as M
-        p = M.Project.query.get(shortname=project_name, neighborhood_id=neighborhood._id)
-        return bool(p)
-
     def suggest_name(self, project_name, neighborhood):
         """Return a suggested project shortname for the full ``project_name``.
 
@@ -467,46 +465,12 @@ class ProjectRegistrationProvider(object):
             check_shortname = shortname.replace('u/', '', 1)
         else:
             check_shortname = shortname
-        allowed, err = self.allowed_project_shortname(check_shortname, neighborhood)
-        if not allowed:
-            raise ValueError('Invalid project shortname: %s error: %s' % (shortname, err))
+        self.shortname_validator.to_python(check_shortname, neighborhood=neighborhood)
 
         p = M.Project.query.get(shortname=shortname, neighborhood_id=neighborhood._id)
         if p:
             raise forge_exc.ProjectConflict('%s already exists in nbhd %s' % (shortname, neighborhood._id))
 
-    def valid_project_shortname(self, shortname, neighborhood):
-        """Determine if the project shortname appears to be valid.
-
-        Returns a pair of values, the first being a bool indicating whether
-        the name appears to be valid, and the second being a message indicating
-        the reason, if any, why the name is invalid.
-
-        NB: Even if a project shortname is valid, it might still not be
-        allowed (it could already be taken, for example).  Use the method
-        ``allowed_project_shortname`` instead to check if the shortname can
-        actually be used.
-        """
-        if not h.re_project_name.match(shortname):
-            return (False, 'Please use only letters, numbers, and dashes 3-15 characters long.')
-        return (True, None)
-
-    def allowed_project_shortname(self, shortname, neighborhood):
-        """Determine if a project shortname can be used.
-
-        A shortname can be used if it is valid and is not already taken.
-
-        Returns a pair of values, the first being a bool indicating whether
-        the name can be used, and the second being a message indicating the
-        reason, if any, why the name cannot be used.
-        """
-        valid, reason = self.valid_project_shortname(shortname, neighborhood)
-        if not valid:
-            return (False, reason)
-        if self._name_taken(shortname, neighborhood):
-            return (False, 'This project name is taken.')
-        return (True, None)
-
     def _create_project(self, neighborhood, shortname, project_name, user, user_project, private_project, apps):
         '''
         Actually create the project, no validation.  This should not be called directly

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/fff526fe/Allura/allura/lib/widgets/forms.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/widgets/forms.py b/Allura/allura/lib/widgets/forms.py
index a5ebe15..ae90b26 100644
--- a/Allura/allura/lib/widgets/forms.py
+++ b/Allura/allura/lib/widgets/forms.py
@@ -22,6 +22,7 @@ from allura.lib import validators as V
 from allura.lib import helpers as h
 from allura.lib import plugin
 from allura.lib.widgets import form_fields as ffw
+from allura.lib import exceptions as forge_exc
 from allura import model as M
 from datetime import datetime
 
@@ -46,14 +47,33 @@ class _HTMLExplanation(ew.InputField):
         'jinja2')
 
 class NeighborhoodProjectShortNameValidator(fev.FancyValidator):
-
-    def to_python(self, value, state):
+    def _validate_shortname(self, shortname, neighborhood, state):
+        if not h.re_project_name.match(shortname):
+            raise forge_exc.ProjectShortnameInvalid(
+                    'Please use only letters, numbers, and dashes 3-15 characters long.',
+                    shortname, state)
+
+    def _validate_allowed(self, shortname, neighborhood, state):
+        p = M.Project.query.get(shortname=shortname, neighborhood_id=neighborhood._id)
+        if p:
+            raise forge_exc.ProjectConflict(
+                    'This project name is taken.',
+                    shortname, state)
+
+    def to_python(self, value, state=None, check_allowed=True, neighborhood=None):
+        """
+        Validate a project shortname.
+
+        If check_allowed is False, the shortname will only be checked for
+        correctness.  Otherwise, it will be rejected if already in use or
+        otherwise disallowed.
+        """
+        if neighborhood is None:
+            neighborhood = M.Neighborhood.query.get(name=state.full_dict['neighborhood'])
         value = h.really_unicode(value or '').encode('utf-8').lower()
-        neighborhood = M.Neighborhood.query.get(name=state.full_dict['neighborhood'])
-        provider = plugin.ProjectRegistrationProvider.get()
-        allowed, message = provider.allowed_project_shortname(value, neighborhood)
-        if not allowed:
-            raise formencode.Invalid(message, value, state)
+        self._validate_shortname(value, neighborhood, state)
+        if check_allowed:
+            self._validate_allowed(value, neighborhood, state)
         return value
 
 class ForgeForm(ew.SimpleForm):
@@ -780,7 +800,7 @@ class NeighborhoodAddProjectForm(ForgeForm):
                 V.MaxBytesValidator(max=40)))
         project_unixname = ew.InputField(
             label='Short Name', field_type='text',
-            validator=NeighborhoodProjectShortNameValidator())
+            validator=None)  # will be set in __init__
         tools = ew.CheckboxSet(name='tools', options=[
             ## Required for Neighborhood functional tests to pass
             ew.Option(label='Wiki', html_value='wiki', selected=True)
@@ -788,6 +808,9 @@ class NeighborhoodAddProjectForm(ForgeForm):
 
     def __init__(self, *args, **kwargs):
         super(NeighborhoodAddProjectForm, self).__init__(*args, **kwargs)
+        # get the shortname validator from the provider
+        provider = plugin.ProjectRegistrationProvider.get()
+        self.fields.project_unixname.validator = provider.shortname_validator
         ## Dynamically generating CheckboxSet of installable tools
         from allura.lib.widgets import forms
         self.fields.tools.options = [

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/fff526fe/Allura/allura/tests/test_plugin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_plugin.py b/Allura/allura/tests/test_plugin.py
index 712fe52..57f8dbf 100644
--- a/Allura/allura/tests/test_plugin.py
+++ b/Allura/allura/tests/test_plugin.py
@@ -15,12 +15,15 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-from nose.tools import assert_equals
+from functools import partial
+from nose.tools import assert_equals, assert_raises
 from mock import Mock, MagicMock, patch
+from formencode import Invalid
 
 from allura import model as M
 from allura.lib.utils import TruthyCallable
 from allura.lib.plugin import ProjectRegistrationProvider
+from allura.lib.exceptions import ProjectConflict, ProjectShortnameInvalid
 
 
 class TestProjectRegistrationProvider(object):
@@ -46,32 +49,15 @@ class TestProjectRegistrationProvider(object):
         assert_equals(f('A More Than Fifteen Character Name', Mock()),
                 'amorethanfifteencharactername')
 
-    def test_valid_project_shortname(self):
-        f = self.provider.valid_project_shortname
-        p = Mock()
-        valid = (True, None)
-        invalid = (False,
-                'Please use only letters, numbers, and dashes '
-                '3-15 characters long.')
-        assert_equals(f('thisislegit', p), valid)
-        assert_equals(f('not valid', p), invalid)
-        assert_equals(f('this-is-valid-but-too-long', p), invalid)
-        assert_equals(f('this is invalid and too long', p), invalid)
-
-    def test_allowed_project_shortname(self):
-        allowed = valid = (True, None)
-        invalid = (False, 'invalid')
-        taken = (False, 'This project name is taken.')
-        cases = [
-                (valid, False, allowed),
-                (invalid, False, invalid),
-                (valid, True, taken),
-            ]
-        p = Mock()
-        vps = self.provider.valid_project_shortname = Mock()
-        nt = self.provider._name_taken = Mock()
-        f = self.provider.allowed_project_shortname
-        for vps_v, nt_v, result in cases:
-            vps.return_value = vps_v
-            nt.return_value = nt_v
-            assert_equals(f('project', p), result)
+    @patch('allura.model.Project')
+    def test_shortname_validator(self, Project):
+        Project.query.get.return_value = None
+        nbhd = Mock()
+        v = self.provider.shortname_validator.to_python
+
+        v('thisislegit', neighborhood=nbhd)
+        assert_raises(ProjectShortnameInvalid, v, 'not valid', neighborhood=nbhd)
+        assert_raises(ProjectShortnameInvalid, v, 'this-is-valid-but-too-long', neighborhood=nbhd)
+        assert_raises(ProjectShortnameInvalid, v, 'this is invalid and too long', neighborhood=nbhd)
+        Project.query.get.return_value = Mock()
+        assert_raises(ProjectConflict, v, 'thisislegit', neighborhood=nbhd)