[#5230] Move SF-specific post-repo-clone handling our of allura core.

- Also added new 'repo_cloned_failed' event
diff --git a/Allura/allura/tasks/repo_tasks.py b/Allura/allura/tasks/repo_tasks.py
index ee7e58f..369b3f9 100644
--- a/Allura/allura/tasks/repo_tasks.py
+++ b/Allura/allura/tasks/repo_tasks.py
@@ -1,13 +1,10 @@
 import shutil
 import logging
-import traceback
 
 from pylons import c
 
 from allura.lib.decorators import task
 from allura.lib.repository import RepositoryApp
-from allura.lib import helpers as h
-from allura.tasks.mail_tasks import sendmail
 
 @task
 def init(**kwargs):
@@ -19,57 +16,16 @@
             c.project.shortname, c.app.config.options.mount_point))
 
 @task
-def clone(
-    cloned_from_path,
-    cloned_from_name,
-    cloned_from_url):
-    try:
-        from allura import model as M
-        c.app.repo.init_as_clone(
-            cloned_from_path,
-            cloned_from_name,
-            cloned_from_url)
-        M.Notification.post_user(
-            c.user, c.app.repo, 'created',
-            text='Repository %s/%s created' % (
-                c.project.shortname, c.app.config.options.mount_point))
-        if not c.project.suppress_emails:
-            sendmail(
-                destinations=[str(c.user._id)],
-                fromaddr=u'SourceForge.net <noreply+project-upgrade@in.sf.net>',
-                reply_to=u'noreply@in.sf.net',
-                subject=u'SourceForge Repo Clone Complete',
-                message_id=h.gen_message_id(),
-                text=u''.join([
-                    u'Your cloned repository %s in project %s is now ready for use.\n\n',
-                    u'Old repository url: %s \n\n',
-                    u'New repository checkout command: %s \n\n',
-                    u'You and any other developers should do a fresh checkout using the ',
-                    u'new repository location.\n'
-                ]) % (c.app.config.options.mount_point, c.project.shortname, cloned_from_url, c.app.repo.clone_command('rw')))
-    except:
-        sendmail(
-            destinations=['sfengineers@geek.net'],
-            fromaddr=u'SourceForge.net <noreply+project-upgrade@in.sf.net>',
-            reply_to=u'noreply@in.sf.net',
-            subject=u'SourceForge Repo Clone Failure',
-            message_id=h.gen_message_id(),
-            text=u''.join([
-                u'Forking/cloning repo %s in project %s from %s failed.\n',
-                u'\n',
-                u'%s',
-            ]) % (c.app.config.options.mount_point, c.project.shortname, cloned_from_url, traceback.format_exc()))
-        if not c.project.suppress_emails:
-            sendmail(
-                destinations=[str(c.user._id)],
-                fromaddr=u'SourceForge.net <noreply+project-upgrade@in.sf.net>',
-                reply_to=u'noreply@in.sf.net',
-                subject=u'SourceForge Repo Clone Failed',
-                message_id=h.gen_message_id(),
-                text=u''.join([
-                    u'Forking/cloning repo %s in project %s from %s failed. ',
-                    u'The SourceForge engineering team has been notified.\n',
-                ]) % (c.app.config.options.mount_point, c.project.shortname, cloned_from_url))
+def clone(cloned_from_path, cloned_from_name, cloned_from_url):
+    from allura import model as M
+    c.app.repo.init_as_clone(
+        cloned_from_path,
+        cloned_from_name,
+        cloned_from_url)
+    M.Notification.post_user(
+        c.user, c.app.repo, 'created',
+        text='Repository %s/%s created' % (
+            c.project.shortname, c.app.config.options.mount_point))
 
 @task
 def reclone(*args, **kwargs):
diff --git a/ForgeGit/forgegit/model/git_repo.py b/ForgeGit/forgegit/model/git_repo.py
index 4624600..d58b02d 100644
--- a/ForgeGit/forgegit/model/git_repo.py
+++ b/ForgeGit/forgegit/model/git_repo.py
@@ -3,6 +3,7 @@
 import string
 import logging
 import random
+import traceback
 from collections import namedtuple
 from datetime import datetime
 from glob import glob
@@ -107,11 +108,12 @@
             self.__dict__['_git'] = repo
             self._setup_special_files(source_url)
         except:
+            g.post_event('repo_clone_failed', source_url, traceback.format_exc())
             self._repo.status = 'ready'
             session(self._repo).flush(self._repo)
             raise
         log.info('... %r cloned', self._repo)
-        g.post_event('repo_cloned')
+        g.post_event('repo_cloned', source_url)
         self._repo.refresh(notify=False)
 
     def commit(self, rev):
diff --git a/ForgeGit/forgegit/tests/model/test_repository.py b/ForgeGit/forgegit/tests/model/test_repository.py
index deb8b63..756a36f 100644
--- a/ForgeGit/forgegit/tests/model/test_repository.py
+++ b/ForgeGit/forgegit/tests/model/test_repository.py
@@ -148,7 +148,8 @@
         assert os.path.exists('/tmp/testgit.git/hooks/post-receive')
         assert os.access('/tmp/testgit.git/hooks/post-receive', os.X_OK)
 
-    def test_clone(self):
+    @mock.patch('forgegit.model.git_repo.g.post_event')
+    def test_clone(self, post_event):
         repo = GM.Repository(
             name='testgit.git',
             fs_path='/tmp/',
@@ -162,6 +163,7 @@
             shutil.rmtree(dirname)
         repo.init()
         repo._impl.clone_from(repo_path)
+        post_event.assert_any_call('repo_cloned', repo_path)
         assert len(repo.log())
         assert not os.path.exists('/tmp/testgit.git/hooks/update')
         assert not os.path.exists('/tmp/testgit.git/hooks/post-receive-user')
@@ -172,6 +174,24 @@
         self.assertIn('exec $DIR/post-receive-user\n', c)
         shutil.rmtree(dirname)
 
+    @mock.patch('forgegit.model.git_repo.g.post_event')
+    @mock.patch('forgegit.model.git_repo.traceback')
+    def test_clone_from_posts_event_on_failure(self, traceback, post_event):
+        fake_source_url = 'fake_source_url'
+        fake_traceback = 'fake_traceback'
+        traceback.format_exc.return_value = fake_traceback
+        repo = GM.Repository(
+            name='testgit.git',
+            fs_path='/tmp/',
+            url_path = '/test/',
+            tool = 'git',
+            status = 'creating')
+        try:
+            repo._impl.clone_from(fake_source_url)
+        except:
+            pass
+        post_event.assert_any_call('repo_clone_failed', fake_source_url, fake_traceback)
+
     def test_index(self):
         i = self.repo.index()
         assert i['type_s'] == 'Git Repository', i
diff --git a/ForgeHg/forgehg/model/hg.py b/ForgeHg/forgehg/model/hg.py
index 0212b4d..7e00971 100644
--- a/ForgeHg/forgehg/model/hg.py
+++ b/ForgeHg/forgehg/model/hg.py
@@ -2,6 +2,7 @@
 import re
 import shutil
 import logging
+import traceback
 from binascii import b2a_hex
 from datetime import datetime
 from cStringIO import StringIO
@@ -94,11 +95,12 @@
             self.__dict__['_hg'] = repo
             self._setup_special_files(source_url)
         except:
+            g.post_event('repo_clone_failed', source_url, traceback.format_exc())
             self._repo.status = 'raise'
             session(self._repo).flush(self._repo)
             raise
         log.info('... %r cloned', self._repo)
-        g.post_event('repo_cloned')
+        g.post_event('repo_cloned', source_url)
         self._repo.refresh(notify=False)
 
     def commit(self, rev):
diff --git a/ForgeHg/forgehg/tests/model/test_repository.py b/ForgeHg/forgehg/tests/model/test_repository.py
index c88a406..ddedac2 100644
--- a/ForgeHg/forgehg/tests/model/test_repository.py
+++ b/ForgeHg/forgehg/tests/model/test_repository.py
@@ -143,7 +143,8 @@
         assert not os.path.exists('/tmp/testrepo.hg/.hg/undo.branch')
         shutil.rmtree(dirname)
 
-    def test_clone(self):
+    @mock.patch('forgehg.model.hg.g.post_event')
+    def test_clone(self, post_event):
         repo = HM.Repository(
             name='testrepo.hg',
             fs_path='/tmp/',
@@ -157,6 +158,7 @@
             shutil.rmtree(dirname)
         repo.init()
         repo._impl.clone_from(repo_path)
+        post_event.assert_any_call('repo_cloned', repo_path)
         assert len(repo.log())
         assert not os.path.exists('/tmp/testrepo.hg/.hg/external-changegroup')
         assert not os.path.exists('/tmp/testrepo.hg/.hg/nested/nested-file')
@@ -171,6 +173,24 @@
         assert not os.path.exists('/tmp/testrepo.hg/.hg/undo.branch')
         shutil.rmtree(dirname)
 
+    @mock.patch('forgehg.model.hg.g.post_event')
+    @mock.patch('forgehg.model.hg.traceback')
+    def test_clone_from_posts_event_on_failure(self, traceback, post_event):
+        fake_source_url = 'fake_source_url'
+        fake_traceback = 'fake_traceback'
+        traceback.format_exc.return_value = fake_traceback
+        repo = HM.Repository(
+            name='testrepo.hg',
+            fs_path='/tmp/',
+            url_path = '/test/',
+            tool = 'hg',
+            status = 'creating')
+        try:
+            repo._impl.clone_from(fake_source_url)
+        except:
+            pass
+        post_event.assert_any_call('repo_clone_failed', fake_source_url, fake_traceback)
+
     def test_index(self):
         i = self.repo.index()
         assert i['type_s'] == 'Hg Repository', i
diff --git a/ForgeSVN/forgesvn/model/svn.py b/ForgeSVN/forgesvn/model/svn.py
index e8173d8..85b4670 100644
--- a/ForgeSVN/forgesvn/model/svn.py
+++ b/ForgeSVN/forgesvn/model/svn.py
@@ -169,6 +169,7 @@
             p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
             stdout, stderr = p.communicate(input='p\n')
             if p.returncode != 0:
+                g.post_event('repo_clone_failed', source_url, stderr)
                 self._repo.status = 'ready'
                 session(self._repo).flush(self._repo)
                 raise SVNCalledProcessError(cmd, p.returncode, stdout, stderr)
@@ -186,7 +187,7 @@
                           c.app.config.options['checkout_url'])):
             c.app.config.options['checkout_url'] = ""
         self._setup_special_files(source_url)
-        g.post_event('repo_cloned')
+        g.post_event('repo_cloned', source_url)
         self._repo.refresh(notify=False)
 
     def refresh_heads(self):
diff --git a/ForgeSVN/forgesvn/tests/model/test_repository.py b/ForgeSVN/forgesvn/tests/model/test_repository.py
index 9e9270c..d317b70 100644
--- a/ForgeSVN/forgesvn/tests/model/test_repository.py
+++ b/ForgeSVN/forgesvn/tests/model/test_repository.py
@@ -144,7 +144,8 @@
         self.assertIn('exec $DIR/post-commit-user "$@"\n', c)
         shutil.rmtree(dirname)
 
-    def test_clone(self):
+    @mock.patch('forgesvn.model.svn.g.post_event')
+    def test_clone(self, post_event):
         repo = SM.Repository(
             name='testsvn',
             fs_path='/tmp/',
@@ -158,6 +159,7 @@
             shutil.rmtree(dirname)
         repo.init()
         repo._impl.clone_from('file://' + repo_path)
+        post_event.assert_any_call('repo_cloned', 'file://' + repo_path)
         assert len(repo.log())
         assert os.path.exists('/tmp/testsvn/hooks/pre-revprop-change')
         assert os.access('/tmp/testsvn/hooks/pre-revprop-change', os.X_OK)
@@ -172,6 +174,26 @@
         self.assertIn('exec $DIR/post-commit-user "$@"\n', c)
         shutil.rmtree(dirname)
 
+    @mock.patch('forgesvn.model.svn.g.post_event')
+    @mock.patch('forgesvn.model.svn.Popen')
+    def test_clone_from_posts_event_on_failure(self, popen, post_event):
+        fake_source_url = 'fake_source_url'
+        fake_traceback = 'fake_traceback'
+        popen_mock = mock.Mock(returncode=1)
+        popen_mock.communicate.return_value = '', fake_traceback
+        popen.return_value = popen_mock
+        repo = SM.Repository(
+            name='testsvn',
+            fs_path='/tmp/',
+            url_path = '/test/',
+            tool = 'svn',
+            status = 'creating')
+        try:
+            repo._impl.clone_from(fake_source_url)
+        except:
+            pass
+        post_event.assert_any_call('repo_clone_failed', fake_source_url, fake_traceback)
+
     def test_index(self):
         i = self.repo.index()
         assert i['type_s'] == 'SVN Repository', i