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/17 00:04:18 UTC

[1/3] git commit: [#6464] Fixed issues from rebase

Updated Branches:
  refs/heads/cj/6464 392b66ce5 -> 7474e6e55 (forced update)


[#6464] Fixed issues from rebase

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


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

Branch: refs/heads/cj/6464
Commit: 28c2c48301fdb31a377a4e106a0fc9bf7870f7dc
Parents: 592fced
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri Aug 16 02:06:33 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 20:01:42 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/helpers.py                          |  3 +++
 Allura/allura/tests/test_helpers.py                   |  9 +++++----
 ForgeImporters/forgeimporters/google/__init__.py      |  4 ++--
 .../tests/google/functional/test_tracker.py           | 14 ++++++++------
 4 files changed, 18 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/28c2c483/Allura/allura/lib/helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 01586c2..39bf250 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -956,6 +956,9 @@ def plain2markdown(text, preserve_multiple_spaces=False, has_html_entities=False
     if preserve_multiple_spaces:
         text = text.replace('\t', ' ' * 4)
         text = re_preserve_spaces.sub('&nbsp;', text)
+    else:
+        text = re_leading_spaces.sub('', text)
+    try:
         # try to use html2text for most of the escaping
         import html2text
         html2text.BODY_WIDTH = 0

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/28c2c483/Allura/allura/tests/test_helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 03a530c..4a0b80d 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -292,17 +292,18 @@ M & Ms - doesn't get escaped
 http://blah.com/?x=y&a=b - not escaped either
 '''
 
-    assert_equals(h.plain2markdown(text), expected)
+    dd.assert_equal(h.plain2markdown(text), expected)
 
-    assert_equals(h.plain2markdown('a foo  bar\n\n    code here?', preserve_multiple_spaces=True),
+    dd.assert_equal(h.plain2markdown('a foo  bar\n\n    code here?', preserve_multiple_spaces=True),
                 'a foo&nbsp; bar\n\n&nbsp;&nbsp;&nbsp; code here?')
 
-    assert_equals(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=True),
+    dd.assert_equal(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=True),
                  '&nbsp;&nbsp;&nbsp; tab before \(stuff\)')
 
-    assert_equals(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=False),
+    dd.assert_equal(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=False),
                  'tab before \(stuff\)')
 
+
 @td.without_module('html2text')
 def test_plain2markdown():
     """Test plain2markdown using fallback regexp to escape markdown.

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/28c2c483/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index f60bc58..7234dc8 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -156,7 +156,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
     @classmethod
     def _get_issue_ids_page(cls, project_name, start):
         url = cls.PAGE_MAP['issues_csv'].format(project_name=project_name, start=start)
-        with closing(urllib2.urlopen(url)) as fp:
+        with closing(cls.urlopen(url)) as fp:
             lines = fp.readlines()[1:]  # skip CSV header
             if not lines[-1].startswith('"'):
                 lines.pop()  # skip "next page here" info footer
@@ -285,6 +285,6 @@ class Attachment(object):
 
     @property
     def file(self):
-        fp_ish = urllib2.urlopen(self.url)
+        fp_ish = GoogleCodeProjectExtractor(None).urlopen(self.url)
         fp = StringIO(fp_ish.read())
         return fp

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/28c2c483/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
index 7d67255..62c4453 100644
--- a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
@@ -29,15 +29,17 @@ from IPython.testing.decorators import module_not_available, skipif
 from alluratest.controller import setup_basic_test
 from allura.tests.decorators import without_module
 from allura import model as M
+from allura.lib import helpers as h
 from forgetracker import model as TM
-from .... import google
-from ....google import tracker
+from forgeimporters import base
+from forgeimporters import google
+from forgeimporters.google import tracker
 
 
 class TestGCTrackerImporter(TestCase):
     def _make_extractor(self, html):
-        with mock.patch.object(google, 'urllib2') as urllib2:
-            urllib2.urlopen.return_value = ''
+        with mock.patch.object(base.h, 'urlopen') as urlopen:
+            urlopen.return_value = ''
             extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
         extractor.page = BeautifulSoup(html)
         extractor.url = "http://test/issue/?id=1"
@@ -45,9 +47,9 @@ class TestGCTrackerImporter(TestCase):
 
     def _make_ticket(self, issue):
         self.assertIsNone(self.project.app_instance('test-issue'))
-        with mock.patch.object(google, 'urllib2') as urllib2,\
+        with mock.patch.object(base.h, 'urlopen') as urlopen,\
              mock.patch.object(google.tracker, 'GoogleCodeProjectExtractor') as GPE:
-            urllib2.urlopen = lambda url: mock.Mock(read=lambda: url)
+            urlopen.side_effect = lambda req, **kw: mock.Mock(read=req.get_full_url)
             GPE.iter_issues.return_value = [issue]
             gti = google.tracker.GoogleCodeTrackerImporter()
             gti.import_tool(self.project, self.user, 'test-issue-project', mount_point='test-issue')


[2/3] git commit: [#6464] Handle non-linked Google users in tracker import

Posted by jo...@apache.org.
[#6464] Handle non-linked Google users in tracker import

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

Branch: refs/heads/cj/6464
Commit: 283554b485316c9958c6878e2a5759c41b577a6e
Parents: 28c2c48
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri Aug 16 20:02:17 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 20:02:17 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/google/__init__.py           | 19 +++++++++----
 ForgeImporters/forgeimporters/google/tracker.py |  4 +--
 .../tests/data/google/empty-issue.html          |  2 +-
 .../tests/google/functional/test_tracker.py     |  2 +-
 .../tests/google/test_extractor.py              | 29 +++++++++++++++-----
 .../forgeimporters/tests/google/test_tracker.py | 11 ++------
 6 files changed, 43 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/283554b4/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 7234dc8..22be7c2 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -198,7 +198,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
             return self.get_issue_created_date()
 
     def get_issue_creator(self):
-        a = self.page.find(id='hc0').find('a', 'userlink')
+        a = self.page.find(id='hc0').find(True, 'userlink')
         return UserLink(a)
 
     def get_issue_status(self):
@@ -209,7 +209,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
             return ''
 
     def get_issue_owner(self):
-        tag = self.page.find(id='issuemeta').find('th', text=re.compile('Owner:')).findNext().a
+        tag = self.page.find(id='issuemeta').find('th', text=re.compile('Owner:')).findNext().find(True, 'userlink')
         if tag:
             return UserLink(tag)
         else:
@@ -233,11 +233,20 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
 class UserLink(object):
     def __init__(self, tag):
         self.name = tag.string.strip()
-        self.link = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.get('href'))
+        if 'href' in tag.attrMap:
+            self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.attrMap['href'])
+        else:
+            self.url = None
+
+    def __str__(self):
+        if self.url:
+            return '[{name}]({url})'.format(name = self.name, url = self.url)
+        else:
+            return self.name
 
 class Comment(object):
     def __init__(self, tag):
-        self.author = UserLink(tag.find('span', 'author').find('a', 'userlink'))
+        self.author = UserLink(tag.find('span', 'author').find(True, 'userlink'))
         self.created_date = tag.find('span', 'date').get('title')
         self.body = _as_text(tag.find('pre')).strip()
         self._get_updates(tag)
@@ -262,7 +271,7 @@ class Comment(object):
     @property
     def annotated_text(self):
         text = (
-                u'*Originally posted by:* [{author.name}]({author.link})\n'
+                u'*Originally posted by:* {author}\n'
                 u'\n'
                 u'{body}\n'
                 u'\n'

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/283554b4/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 3257c5a..3d9843c 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -86,11 +86,11 @@ class GoogleCodeTrackerImporter(ToolImporter):
         ticket.mod_date = datetime.strptime(issue.get_issue_mod_date(), '%c')
         owner = issue.get_issue_owner()
         if owner:
-            owner_line = '*Originally owned by:* [{owner.name}]({owner.link})\n'.format(owner=owner)
+            owner_line = '*Originally owned by:* {owner}\n'.format(owner=owner)
         else:
             owner_line = ''
         ticket.description = (
-                u'*Originally created by:* [{creator.name}]({creator.link})\n'
+                u'*Originally created by:* {creator}\n'
                 u'{owner}'
                 u'\n'
                 u'{body}').format(

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/283554b4/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html b/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
index a8fb1d5..b2eef20 100644
--- a/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
+++ b/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
@@ -240,7 +240,7 @@ contributes to open source, such as <a href="http://www.firefox.com">Firefox</a>
  Reported by
 
 
- <a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a>,
+ <span class="userlink">john...@gmail.com</span>,
  <span class="date" title="Thu Aug  8 14:56:23 2013">Today (15 minutes ago)</span>
 </div>
 <pre>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/283554b4/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
index 62c4453..80a8747 100644
--- a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
@@ -70,7 +70,7 @@ class TestGCTrackerImporter(TestCase):
     def test_empty_issue(self):
         ticket = self._make_ticket(self.empty_issue)
         self.assertEqual(ticket.summary, 'Empty Issue')
-        self.assertEqual(ticket.description, '*Originally created by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n\nEmpty')
+        self.assertEqual(ticket.description, '*Originally created by:* john...@gmail.com\n\nEmpty')
         self.assertEqual(ticket.status, '')
         self.assertEqual(ticket.milestone, '')
         self.assertEqual(ticket.custom_fields, {})

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/283554b4/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 7a485d6..4157c98 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -152,9 +152,9 @@ class TestGoogleCodeProjectExtractor(TestCase):
         test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
         gpe = self._make_extractor(test_issue)
         self.assertEqual(gpe.get_issue_creator().name, 'john...@gmail.com')
-        self.assertEqual(gpe.get_issue_creator().link, 'http://code.google.com/u/101557263855536553789/')
+        self.assertEqual(gpe.get_issue_creator().url, 'http://code.google.com/u/101557263855536553789/')
         self.assertEqual(gpe.get_issue_owner().name, 'john...@gmail.com')
-        self.assertEqual(gpe.get_issue_owner().link, 'http://code.google.com/u/101557263855536553789/')
+        self.assertEqual(gpe.get_issue_owner().url, 'http://code.google.com/u/101557263855536553789/')
         self.assertEqual(gpe.get_issue_status(), 'Started')
         self.assertEqual(gpe.get_issue_summary(), 'Test Issue')
         self.assertEqual(gpe.get_issue_description(),
@@ -217,7 +217,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
         expected = [
                 {
                     'author.name': 'john...@gmail.com',
-                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'author.url': 'http://code.google.com/u/101557263855536553789/',
                     'created_date': 'Thu Aug  8 15:35:15 2013',
                     'body': 'Test *comment* is a comment',
                     'updates': {'Status:': 'Started', 'Labels:': '-OpSys-Linux OpSys-Windows'},
@@ -225,7 +225,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
                 },
                 {
                     'author.name': 'john...@gmail.com',
-                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'author.url': 'http://code.google.com/u/101557263855536553789/',
                     'created_date': 'Thu Aug  8 15:35:34 2013',
                     'body': 'Another comment',
                     'updates': {},
@@ -233,7 +233,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
                 },
                 {
                     'author.name': 'john...@gmail.com',
-                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'author.url': 'http://code.google.com/u/101557263855536553789/',
                     'created_date': 'Thu Aug  8 15:36:39 2013',
                     'body': 'Last comment',
                     'updates': {},
@@ -241,7 +241,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
                 },
                 {
                     'author.name': 'john...@gmail.com',
-                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'author.url': 'http://code.google.com/u/101557263855536553789/',
                     'created_date': 'Thu Aug  8 15:36:57 2013',
                     'body': 'Oh, I forgot one',
                     'updates': {'Labels:': 'OpSys-OSX'},
@@ -250,8 +250,23 @@ class TestGoogleCodeProjectExtractor(TestCase):
             ]
         for actual, expected in zip(comments, expected):
             self.assertEqual(actual.author.name, expected['author.name'])
-            self.assertEqual(actual.author.link, expected['author.link'])
+            self.assertEqual(actual.author.url, expected['author.url'])
             self.assertEqual(actual.created_date, expected['created_date'])
             self.assertEqual(actual.body, expected['body'])
             self.assertEqual(actual.updates, expected['updates'])
             self.assertEqual([a.filename for a in actual.attachments], expected['attachments'])
+
+class TestUserLink(TestCase):
+    def test_plain(self):
+        tag = mock.Mock()
+        tag.string.strip.return_value = 'name'
+        tag.attrMap = {}
+        link = google.UserLink(tag)
+        self.assertEqual(str(link), 'name')
+
+    def test_linked(self):
+        tag = mock.Mock()
+        tag.string.strip.return_value = 'name'
+        tag.attrMap = {'href': '/p/project'}
+        link = google.UserLink(tag)
+        self.assertEqual(str(link), '[name](http://code.google.com/p/project)')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/283554b4/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 15b8f33..01d9f8e 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -112,26 +112,21 @@ class TestTrackerImporter(TestCase):
 
     def test_process_fields(self):
         ticket = mock.Mock()
-        def _user(l):
-            u = mock.Mock()
-            u.name = '%sname' % l
-            u.link = '%slink' % l
-            return u
         issue = mock.Mock(
                 get_issue_summary=lambda:'summary',
                 get_issue_description=lambda:'my *description* fool',
                 get_issue_status=lambda:'status',
                 get_issue_created_date=lambda:'created_date',
                 get_issue_mod_date=lambda:'mod_date',
-                get_issue_creator=lambda:_user('c'),
-                get_issue_owner=lambda:_user('o'),
+                get_issue_creator=lambda:'creator',
+                get_issue_owner=lambda:'owner',
             )
         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, '*Originally created by:* [cname](clink)\n*Originally owned by:* [oname](olink)\n\nmy \*description\* fool')
+            self.assertEqual(ticket.description, '*Originally created by:* creator\n*Originally owned by:* owner\n\nmy \*description\* fool')
             self.assertEqual(ticket.status, 'status')
             self.assertEqual(ticket.created_date, 'created_date')
             self.assertEqual(ticket.mod_date, 'mod_date')


[3/3] git commit: [#6464] Fixed handling of attachment links in GC Tracker importer

Posted by jo...@apache.org.
[#6464] Fixed handling of attachment links in GC Tracker importer

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


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

Branch: refs/heads/cj/6464
Commit: 7474e6e55c333ab64082433e00356f7ea0f0e30f
Parents: 283554b
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri Aug 16 22:04:00 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:04:00 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/__init__.py    | 16 ++++++++--------
 .../forgeimporters/tests/google/test_extractor.py   |  6 +++---
 2 files changed, 11 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/7474e6e5/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 22be7c2..fbf3eb0 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -17,7 +17,7 @@
 
 import re
 import urllib
-from urlparse import urlparse, urljoin
+from urlparse import urlparse, urljoin, parse_qs
 from collections import defaultdict
 from contextlib import closing
 try:
@@ -123,7 +123,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
 
     def get_icon(self, project):
         page = self.get_page('project_info')
-        icon_url = urljoin(self.url, page.find(itemprop='image').attrMap['src'])
+        icon_url = urljoin(self.url, page.find(itemprop='image').get('src'))
         if icon_url == self.DEFAULT_ICON:
             return
         icon_name = urllib.unquote(urlparse(icon_url).path).split('/')[-1]
@@ -222,7 +222,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
     def get_issue_attachments(self):
         attachments = self.page.find(id='hc0').find('div', 'attachments')
         if attachments:
-            return map(Attachment, attachments.findAll('tr'))
+            return [Attachment(a.parent) for a in attachments.findAll('a', text='Download')]
         else:
             return []
 
@@ -233,8 +233,8 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
 class UserLink(object):
     def __init__(self, tag):
         self.name = tag.string.strip()
-        if 'href' in tag.attrMap:
-            self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.attrMap['href'])
+        if tag.get('href'):
+            self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.get('href'))
         else:
             self.url = None
 
@@ -264,7 +264,7 @@ class Comment(object):
     def _get_attachments(self, tag):
         attachments = tag.find('div', 'attachments')
         if attachments:
-            self.attachments = map(Attachment, attachments.findAll('tr'))
+            self.attachments = [Attachment(a.parent) for a in attachments.findAll('a', text='Download')]
         else:
             self.attachments = []
 
@@ -288,8 +288,8 @@ class Comment(object):
 
 class Attachment(object):
     def __init__(self, tag):
-        self.filename = _as_text(tag).strip().split()[0]
-        self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.a.get('href'))
+        self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.get('href'))
+        self.filename = parse_qs(urlparse(self.url).query)['name'][0]
         self.type = None
 
     @property

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/7474e6e5/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 4157c98..e208e8d 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -76,7 +76,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
     def test_get_icon(self, M, StringIO):
         self.urlopen.return_value.info.return_value = {'content-type': 'image/png'}
         extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
-        extractor.page.find.return_value.attrMap = {'src': 'http://example.com/foo/bar/my-logo.png'}
+        extractor.page.find.return_value.get.return_value = 'http://example.com/foo/bar/my-logo.png'
         self.urlopen.reset_mock()
 
         extractor.get_icon(self.project)
@@ -260,13 +260,13 @@ class TestUserLink(TestCase):
     def test_plain(self):
         tag = mock.Mock()
         tag.string.strip.return_value = 'name'
-        tag.attrMap = {}
+        tag.get.return_value = None
         link = google.UserLink(tag)
         self.assertEqual(str(link), 'name')
 
     def test_linked(self):
         tag = mock.Mock()
         tag.string.strip.return_value = 'name'
-        tag.attrMap = {'href': '/p/project'}
+        tag.get.return_value = '/p/project'
         link = google.UserLink(tag)
         self.assertEqual(str(link), '[name](http://code.google.com/p/project)')