[#6964] Create commit activity despite unkown user

Signed-off-by: Tim Van Steenburgh <tvansteenburgh@gmail.com>
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index d5ba927..d364d2c 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -36,6 +36,7 @@
 from allura.model.repo import Commit, Tree, LastCommit, ModelCache
 from allura.model.index import ArtifactReferenceDoc, ShortlinkDoc
 from allura.model.auth import User
+from allura.model.timeline import TransientActor
 
 log = logging.getLogger(__name__)
 
@@ -140,8 +141,10 @@
                 user = User.by_username(new.committed.name)
             if user is not None:
                 g.statsUpdater.newCommit(new, repo.app_config.project, user)
-                g.director.create_activity(user, 'committed', new,
-                                           related_nodes=[repo.app_config.project])
+            actor = user or TransientActor(
+                    activity_name=new.committed.name or new.committed.emai)
+            g.director.create_activity(actor, 'committed', new,
+                                       related_nodes=[repo.app_config.project])
 
     log.info('Refresh complete for %s', repo.full_fs_path)
     g.post_event('repo_refreshed', len(commit_ids), all_commits, new_clone)
diff --git a/Allura/allura/model/timeline.py b/Allura/allura/model/timeline.py
index be538a2..63cbd83 100644
--- a/Allura/allura/model/timeline.py
+++ b/Allura/allura/model/timeline.py
@@ -47,7 +47,8 @@
         super(Director, self).create_activity(actor, verb, obj,
                                               target=target, related_nodes=related_nodes)
         # aggregate actor and follower's timelines
-        create_timelines.post(actor.node_id)
+        if actor.node_id:
+            create_timelines.post(actor.node_id)
         # aggregate project and follower's timelines
         for node in [obj, target] + (related_nodes or []):
             if isinstance(node, Project):
@@ -99,6 +100,16 @@
         return security.has_access(self, perm, user, self.project)
 
 
+class TransientActor(NodeBase, ActivityObjectBase):
+    """An activity actor which is not a persistent Node in the network.
+
+    """
+    def __init__(self, activity_name):
+        NodeBase.__init__(self)
+        ActivityObjectBase.__init__(self)
+        self.activity_name = activity_name
+
+
 def perm_check(user):
     def _perm_check(activity):
         """Return True if c.user has 'read' access to this activity,
diff --git a/ForgeActivity/forgeactivity/templates/macros.html b/ForgeActivity/forgeactivity/templates/macros.html
index c60582e..f3bac05 100644
--- a/ForgeActivity/forgeactivity/templates/macros.html
+++ b/ForgeActivity/forgeactivity/templates/macros.html
@@ -18,7 +18,11 @@
 -#}
 
 {% macro activity_obj(o) %}
-  <a href="{{o.activity_url}}">{{o.activity_name}}</a>
+  {% if o.activity_url %}
+    <a href="{{o.activity_url}}">{{o.activity_name}}</a>
+  {% else %}
+    {{o.activity_name}}
+  {% endif %}
 {% endmacro %}
 
 {% macro icon(o, size, className) -%}