changeset 1722:e7eef7a1db6a beta

#235 forking page repo group selection - group is selected as parent repo - refactoring of session behavior to get more atomic operations in models
author Marcin Kuzminski <marcin@python-works.com>
date Sat, 26 Nov 2011 00:11:32 +0200
parents 05dbebede0d8
children 64e91067b996
files rhodecode/config/routing.py rhodecode/controllers/admin/repos.py rhodecode/controllers/forks.py rhodecode/controllers/journal.py rhodecode/controllers/settings.py rhodecode/lib/celerylib/tasks.py rhodecode/lib/hooks.py rhodecode/lib/utils.py rhodecode/model/forms.py rhodecode/model/notification.py rhodecode/model/repo.py rhodecode/model/scm.py rhodecode/public/css/style.css rhodecode/templates/forks/fork.html rhodecode/templates/settings/repo_fork.html
diffstat 15 files changed, 341 insertions(+), 252 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/config/routing.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/config/routing.py	Sat Nov 26 00:11:32 2011 +0200
@@ -465,19 +465,20 @@
                 conditions=dict(function=check_repo))
 
     rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
-                controller='settings', action='fork_create',
+                controller='forks', action='fork_create',
                 conditions=dict(function=check_repo, method=["POST"]))
 
     rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
-                controller='settings', action='fork',
+                controller='forks', action='fork',
                 conditions=dict(function=check_repo))
 
+    rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
+                 controller='forks', action='forks',
+                 conditions=dict(function=check_repo))
+
     rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
                  controller='followers', action='followers',
                  conditions=dict(function=check_repo))
 
-    rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
-                 controller='forks', action='forks',
-                 conditions=dict(function=check_repo))
 
     return rmap
--- a/rhodecode/controllers/admin/repos.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/controllers/admin/repos.py	Sat Nov 26 00:11:32 2011 +0200
@@ -29,9 +29,10 @@
 from formencode import htmlfill
 
 from paste.httpexceptions import HTTPInternalServerError
-from pylons import request, response, session, tmpl_context as c, url
-from pylons.controllers.util import abort, redirect
+from pylons import request, session, tmpl_context as c, url
+from pylons.controllers.util import redirect
 from pylons.i18n.translation import _
+from sqlalchemy.exc import IntegrityError
 
 from rhodecode.lib import helpers as h
 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
@@ -39,11 +40,11 @@
 from rhodecode.lib.base import BaseController, render
 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
 from rhodecode.lib.helpers import get_token
+from rhodecode.model.meta import Session
 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
 from rhodecode.model.forms import RepoForm
 from rhodecode.model.scm import ScmModel
 from rhodecode.model.repo import RepoModel
-from sqlalchemy.exc import IntegrityError
 
 log = logging.getLogger(__name__)
 
@@ -65,7 +66,7 @@
     def __load_defaults(self):
         c.repo_groups = RepoGroup.groups_choices()
         c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
-        
+
         repo_model = RepoModel()
         c.users_array = repo_model.get_users_js()
         c.users_groups_array = repo_model.get_users_groups_js()
@@ -127,13 +128,13 @@
         """
         POST /repos: Create a new item"""
         # url('repos')
-        repo_model = RepoModel()
+
         self.__load_defaults()
         form_result = {}
         try:
             form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
                             .to_python(dict(request.POST))
-            repo_model.create(form_result, self.rhodecode_user)
+            RepoModel().create(form_result, self.rhodecode_user)
             if form_result['clone_uri']:
                 h.flash(_('created repository %s from %s') \
                     % (form_result['repo_name'], form_result['clone_uri']),
@@ -143,13 +144,13 @@
                     category='success')
 
             if request.POST.get('user_created'):
-                #created by regular non admin user
+                # created by regular non admin user
                 action_logger(self.rhodecode_user, 'user_created_repo',
                               form_result['repo_name_full'], '', self.sa)
             else:
                 action_logger(self.rhodecode_user, 'admin_created_repo',
                               form_result['repo_name_full'], '', self.sa)
-
+            Session().commit()
         except formencode.Invalid, errors:
 
             c.new_repo = errors.value['repo_name']
@@ -207,7 +208,7 @@
             changed_name = repo.repo_name
             action_logger(self.rhodecode_user, 'admin_updated_repo',
                               changed_name, '', self.sa)
-
+            Session().commit()
         except formencode.Invalid, errors:
             defaults = self.__load_data(repo_name)
             defaults.update(errors.value)
@@ -251,7 +252,7 @@
             repo_model.delete(repo)
             invalidate_cache('get_repo_cached_%s' % repo_name)
             h.flash(_('deleted repository %s') % repo_name, category='success')
-
+            Session().commit()
         except IntegrityError, e:
             if e.message.find('repositories_fork_id_fkey'):
                 log.error(traceback.format_exc())
--- a/rhodecode/controllers/forks.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/controllers/forks.py	Sat Nov 26 00:11:32 2011 +0200
@@ -23,13 +23,23 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import logging
+import formencode
+import traceback
+from formencode import htmlfill
 
-from pylons import tmpl_context as c, request
+from pylons import tmpl_context as c, request, url
+from pylons.controllers.util import redirect
+from pylons.i18n.translation import _
+
+import rhodecode.lib.helpers as h
 
 from rhodecode.lib.helpers import Page
-from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
+from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
+    NotAnonymous
 from rhodecode.lib.base import BaseRepoController, render
-from rhodecode.model.db import Repository, User, UserFollowing
+from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
+from rhodecode.model.repo import RepoModel
+from rhodecode.model.forms import RepoForkForm
 
 log = logging.getLogger(__name__)
 
@@ -37,11 +47,59 @@
 class ForksController(BaseRepoController):
 
     @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
     def __before__(self):
         super(ForksController, self).__before__()
 
+    def __load_defaults(self):
+        c.repo_groups = RepoGroup.groups_choices()
+        c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
+
+    def __load_data(self, repo_name=None):
+        """
+        Load defaults settings for edit, and update
+
+        :param repo_name:
+        """
+        self.__load_defaults()
+
+        c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
+        repo = db_repo.scm_instance
+
+        if c.repo_info is None:
+            h.flash(_('%s repository is not mapped to db perhaps'
+                      ' it was created or renamed from the filesystem'
+                      ' please run the application again'
+                      ' in order to rescan repositories') % repo_name,
+                      category='error')
+
+            return redirect(url('repos'))
+
+        c.default_user_id = User.get_by_username('default').user_id
+        c.in_public_journal = UserFollowing.query()\
+            .filter(UserFollowing.user_id == c.default_user_id)\
+            .filter(UserFollowing.follows_repository == c.repo_info).scalar()
+
+        if c.repo_info.stats:
+            last_rev = c.repo_info.stats.stat_on_revision
+        else:
+            last_rev = 0
+        c.stats_revision = last_rev
+
+        c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
+
+        if last_rev == 0 or c.repo_last_rev == 0:
+            c.stats_percentage = 0
+        else:
+            c.stats_percentage = '%.2f' % ((float((last_rev)) /
+                                            c.repo_last_rev) * 100)
+
+        defaults = RepoModel()._get_defaults(repo_name)
+        # add prefix to fork
+        defaults['repo_name'] = 'fork-' + defaults['repo_name']
+        return defaults
+
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
     def forks(self, repo_name):
         p = int(request.params.get('page', 1))
         repo_id = c.rhodecode_db_repo.repo_id
@@ -54,3 +112,63 @@
             return c.forks_data
 
         return render('/forks/forks.html')
+
+    @NotAnonymous()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    def fork(self, repo_name):
+        c.repo_info = Repository.get_by_repo_name(repo_name)
+        if not c.repo_info:
+            h.flash(_('%s repository is not mapped to db perhaps'
+                      ' it was created or renamed from the file system'
+                      ' please run the application again'
+                      ' in order to rescan repositories') % repo_name,
+                      category='error')
+
+            return redirect(url('home'))
+
+        defaults = self.__load_data(repo_name)
+
+        return htmlfill.render(
+            render('forks/fork.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False
+        )
+
+
+    @NotAnonymous()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    def fork_create(self, repo_name):
+        self.__load_defaults()
+        c.repo_info = Repository.get_by_repo_name(repo_name)
+        _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
+                             repo_groups=c.repo_groups_choices,)()
+        form_result = {}
+        try:
+            form_result = _form.to_python(dict(request.POST))
+            # add org_path of repo so we can do a clone from it later
+            form_result['org_path'] = c.repo_info.repo_name
+
+            # create fork is done sometimes async on celery, db transaction
+            # management is handled there.
+            RepoModel().create_fork(form_result, self.rhodecode_user)
+            h.flash(_('forked %s repository as %s') \
+                      % (repo_name, form_result['repo_name']),
+                    category='success')
+        except formencode.Invalid, errors:
+            c.new_repo = errors.value['repo_name']
+
+            return htmlfill.render(
+                render('forks/fork.html'),
+                defaults=errors.value,
+                errors=errors.error_dict or {},
+                prefix_error=False,
+                encoding="UTF-8")
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('An error occurred during repository forking %s') %
+                    repo_name, category='error')
+
+        return redirect(url('home'))
--- a/rhodecode/controllers/journal.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/controllers/journal.py	Sat Nov 26 00:11:32 2011 +0200
@@ -23,21 +23,22 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import logging
+from itertools import groupby
 
 from sqlalchemy import or_
-from sqlalchemy.orm import joinedload, make_transient
+from sqlalchemy.orm import joinedload
 from webhelpers.paginate import Page
-from itertools import groupby
+from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 
 from paste.httpexceptions import HTTPBadRequest
 from pylons import request, tmpl_context as c, response, url
 from pylons.i18n.translation import _
-from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 
 import rhodecode.lib.helpers as h
 from rhodecode.lib.auth import LoginRequired, NotAnonymous
 from rhodecode.lib.base import BaseController, render
 from rhodecode.model.db import UserLog, UserFollowing
+from rhodecode.model.meta import Session
 
 log = logging.getLogger(__name__)
 
@@ -124,6 +125,7 @@
                 try:
                     self.scm_model.toggle_following_user(user_id,
                                                 self.rhodecode_user.user_id)
+                    Session().commit()
                     return 'ok'
                 except:
                     raise HTTPBadRequest()
@@ -133,6 +135,7 @@
                 try:
                     self.scm_model.toggle_following_repo(repo_id,
                                                 self.rhodecode_user.user_id)
+                    Session().commit()
                     return 'ok'
                 except:
                     raise HTTPBadRequest()
--- a/rhodecode/controllers/settings.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/controllers/settings.py	Sat Nov 26 00:11:32 2011 +0200
@@ -35,14 +35,14 @@
 
 import rhodecode.lib.helpers as h
 
-from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
-    HasRepoPermissionAnyDecorator, NotAnonymous
+from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.utils import invalidate_cache, action_logger
 
-from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
+from rhodecode.model.forms import RepoSettingsForm
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.db import RepoGroup
+from rhodecode.model.meta import Session
 
 log = logging.getLogger(__name__)
 
@@ -52,15 +52,15 @@
     @LoginRequired()
     def __before__(self):
         super(SettingsController, self).__before__()
-    
+
     def __load_defaults(self):
         c.repo_groups = RepoGroup.groups_choices()
         c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
-        
+
         repo_model = RepoModel()
         c.users_array = repo_model.get_users_js()
         c.users_groups_array = repo_model.get_users_groups_js()
-        
+
     @HasRepoPermissionAllDecorator('repository.admin')
     def index(self, repo_name):
         repo_model = RepoModel()
@@ -89,15 +89,15 @@
     def update(self, repo_name):
         repo_model = RepoModel()
         changed_name = repo_name
-        
+
         self.__load_defaults()
-        
+
         _form = RepoSettingsForm(edit=True,
                                  old_data={'repo_name': repo_name},
                                  repo_groups=c.repo_groups_choices)()
         try:
             form_result = _form.to_python(dict(request.POST))
-            
+
             repo_model.update(repo_name, form_result)
             invalidate_cache('get_repo_cached_%s' % repo_name)
             h.flash(_('Repository %s updated successfully' % repo_name),
@@ -105,6 +105,7 @@
             changed_name = form_result['repo_name_full']
             action_logger(self.rhodecode_user, 'user_updated_repo',
                           changed_name, '', self.sa)
+            Session().commit()
         except formencode.Invalid, errors:
             c.repo_info = repo_model.get_by_repo_name(repo_name)
             c.users_array = repo_model.get_users_js()
@@ -148,61 +149,10 @@
             repo_model.delete(repo)
             invalidate_cache('get_repo_cached_%s' % repo_name)
             h.flash(_('deleted repository %s') % repo_name, category='success')
+            Session().commit()
         except Exception:
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during deletion of %s') % repo_name,
                     category='error')
 
         return redirect(url('home'))
-
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def fork(self, repo_name):
-        repo_model = RepoModel()
-        c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
-        if not repo:
-            h.flash(_('%s repository is not mapped to db perhaps'
-                      ' it was created or renamed from the file system'
-                      ' please run the application again'
-                      ' in order to rescan repositories') % repo_name,
-                      category='error')
-
-            return redirect(url('home'))
-
-        return render('settings/repo_fork.html')
-
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def fork_create(self, repo_name):
-        repo_model = RepoModel()
-        c.repo_info = repo_model.get_by_repo_name(repo_name)
-        _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type})()
-        form_result = {}
-        try:
-            form_result = _form.to_python(dict(request.POST))
-            form_result.update({'repo_name': repo_name})
-            repo_model.create_fork(form_result, self.rhodecode_user)
-            h.flash(_('forked %s repository as %s') \
-                      % (repo_name, form_result['fork_name']),
-                    category='success')
-            action_logger(self.rhodecode_user,
-                          'user_forked_repo:%s' % form_result['fork_name'],
-                           repo_name, '', self.sa)
-        except formencode.Invalid, errors:
-            c.new_repo = errors.value['fork_name']
-            r = render('settings/repo_fork.html')
-
-            return htmlfill.render(
-                r,
-                defaults=errors.value,
-                errors=errors.error_dict or {},
-                prefix_error=False,
-                encoding="UTF-8")
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('An error occurred during repository forking %s') %
-                    repo_name, category='error')
-
-        return redirect(url('home'))
--- a/rhodecode/lib/celerylib/tasks.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/lib/celerylib/tasks.py	Sat Nov 26 00:11:32 2011 +0200
@@ -37,29 +37,28 @@
 from pylons import config, url
 from pylons.i18n.translation import _
 
+from vcs import get_backend
 
 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
     __get_lockkey, LockHeld, DaemonLock
 from rhodecode.lib.helpers import person
 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
-from rhodecode.lib.utils import add_cache
+from rhodecode.lib.utils import add_cache, action_logger
 from rhodecode.lib.compat import json, OrderedDict
 
 from rhodecode.model import init_model
 from rhodecode.model import meta
-from rhodecode.model.db import RhodeCodeUi, Statistics, Repository, User
-
-from vcs.backends import get_repo
+from rhodecode.model.db import Statistics, Repository, User
 
 from sqlalchemy import engine_from_config
 
-
 add_cache(config)
 
 __all__ = ['whoosh_index', 'get_commits_stats',
            'reset_user_password', 'send_email']
 
+
 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
 
 
@@ -81,17 +80,13 @@
 
     return log
 
-def get_repos_path():
-    sa = get_session()
-    q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
-    return q.ui_value
-
-
 @task(ignore_result=True)
 @locked_task
 def whoosh_index(repo_location, full_index):
+    from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
+
     #log = whoosh_index.get_logger()
-    from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
+
     index_location = config['index_dir']
     WhooshIndexingDaemon(index_location=index_location,
                          repo_location=repo_location, sa=get_session())\
@@ -111,13 +106,12 @@
         sa = get_session()
         lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
 
-        #for js data compatibilty cleans the key for person from '
+        # for js data compatibilty cleans the key for person from '
         akc = lambda k: person(k).replace('"', "")
 
         co_day_auth_aggr = {}
         commits_by_day_aggregate = {}
-        repos_path = get_repos_path()
-        repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
+        repo = Repository.get_by_repo_name(repo_name).scm_instance
         repo_size = len(repo.revisions)
         #return if repo have no revisions
         if repo_size < 1:
@@ -139,9 +133,9 @@
             last_rev = cur_stats.stat_on_revision
 
         if last_rev == repo.get_changeset().revision and repo_size > 1:
-            #pass silently without any work if we're not on first revision or
-            #current state of parsing revision(from db marker) is the
-            #last revision
+            # pass silently without any work if we're not on first revision or
+            # current state of parsing revision(from db marker) is the
+            # last revision
             lock.release()
             return True
 
@@ -255,10 +249,11 @@
 
 @task(ignore_result=True)
 def send_password_link(user_email):
+    from rhodecode.model.notification import EmailNotificationModel
+
     log = get_logger(send_password_link)
 
     try:
-        from rhodecode.model.notification import EmailNotificationModel
         sa = get_session()
         user = User.get_by_email(user_email)
         if user:
@@ -283,9 +278,9 @@
 
 @task(ignore_result=True)
 def reset_user_password(user_email):
-    log = get_logger(reset_user_password)
+    from rhodecode.lib import auth
 
-    from rhodecode.lib import auth
+    log = get_logger(reset_user_password)
 
     try:
         try:
@@ -361,27 +356,39 @@
 
 @task(ignore_result=True)
 def create_repo_fork(form_data, cur_user):
+    """
+    Creates a fork of repository using interval VCS methods
+    
+    :param form_data:
+    :param cur_user:
+    """
+    from rhodecode.model.repo import RepoModel
+
     log = get_logger(create_repo_fork)
 
-    from rhodecode.model.repo import RepoModel
-    from vcs import get_backend
+    Session = get_session()
+    base_path = Repository.base_path()
+
+    RepoModel(Session).create(form_data, cur_user, just_db=True, fork=True)
+
+    alias = form_data['repo_type']
+    org_repo_name = form_data['org_path']
+    source_repo_path = os.path.join(base_path, org_repo_name)
+    destination_fork_path = os.path.join(base_path, form_data['repo_name_full'])
 
-    repo_model = RepoModel(get_session())
-    repo_model.create(form_data, cur_user, just_db=True, fork=True)
-    repo_name = form_data['repo_name']
-    repos_path = get_repos_path()
-    repo_path = os.path.join(repos_path, repo_name)
-    repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
-    alias = form_data['repo_type']
-
-    log.info('creating repo fork %s as %s', repo_name, repo_path)
+    log.info('creating fork of %s as %s', source_repo_path,
+             destination_fork_path)
     backend = get_backend(alias)
-    backend(str(repo_fork_path), create=True, src_url=str(repo_path))
-
+    backend(safe_str(destination_fork_path), create=True,
+            src_url=safe_str(source_repo_path))
+    action_logger(cur_user, 'user_forked_repo:%s' % org_repo_name,
+                   org_repo_name, '', Session)
+    # finally commit at latest possible stage
+    Session.commit()
 
 def __get_codes_stats(repo_name):
-    repos_path = get_repos_path()
-    repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
+    repo = Repository.get_by_repo_name(repo_name).scm_instance
+
     tip = repo.get_changeset()
     code_stats = {}
 
--- a/rhodecode/lib/hooks.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/lib/hooks.py	Sat Nov 26 00:11:32 2011 +0200
@@ -33,7 +33,8 @@
 
 
 def repo_size(ui, repo, hooktype=None, **kwargs):
-    """Presents size of repository after push
+    """
+    Presents size of repository after push
 
     :param ui:
     :param repo:
@@ -65,7 +66,8 @@
 
 
 def log_pull_action(ui, repo, **kwargs):
-    """Logs user last pull action
+    """
+    Logs user last pull action
 
     :param ui:
     :param repo:
@@ -76,13 +78,15 @@
     repository = extra_params['repository']
     action = 'pull'
 
-    action_logger(username, action, repository, extra_params['ip'])
+    action_logger(username, action, repository, extra_params['ip'],
+                  commit=True)
 
     return 0
 
 
 def log_push_action(ui, repo, **kwargs):
-    """Maps user last push action to new changeset id, from mercurial
+    """
+    Maps user last push action to new changeset id, from mercurial
 
     :param ui:
     :param repo:
@@ -110,6 +114,7 @@
 
     action = action % ','.join(revs)
 
-    action_logger(username, action, repository, extra_params['ip'])
+    action_logger(username, action, repository, extra_params['ip'],
+                  commit=True)
 
     return 0
--- a/rhodecode/lib/utils.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/lib/utils.py	Sat Nov 26 00:11:32 2011 +0200
@@ -93,7 +93,7 @@
     return request.environ['pylons.routes_dict'].get('repo_name')
 
 
-def action_logger(user, action, repo, ipaddr='', sa=None):
+def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
     """
     Action logger for various actions made by users
 
@@ -138,12 +138,13 @@
         user_log.action_date = datetime.datetime.now()
         user_log.user_ip = ipaddr
         sa.add(user_log)
-        sa.commit()
 
         log.info('Adding user %s, action %s on %s', user_obj, action, repo)
+        if commit:
+            sa.commit()
     except:
         log.error(traceback.format_exc())
-        sa.rollback()
+        raise
 
 
 def get_repos(path, recursive=False):
--- a/rhodecode/model/forms.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/model/forms.py	Sat Nov 26 00:11:32 2011 +0200
@@ -185,7 +185,7 @@
 class ValidPasswordsMatch(formencode.validators.FancyValidator):
 
     def validate_python(self, value, state):
-        
+
         pass_val = value.get('password') or value.get('new_password')
         if pass_val != value['password_confirmation']:
             e_dict = {'password_confirmation':
@@ -198,7 +198,7 @@
         'invalid_login':_('invalid user name'),
         'disabled_account':_('Your account is disabled')
     }
-    
+
     # error mapping
     e_dict = {'username':messages['invalid_login'],
               'password':messages['invalid_password']}
@@ -208,7 +208,7 @@
         password = value['password']
         username = value['username']
         user = User.get_by_username(username)
-        
+
         if authenticate(username, password):
             return value
         else:
@@ -254,7 +254,7 @@
                 # db key This is an actual just the name to store in the
                 # database
                 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
-                
+
             else:
                 group_path = ''
                 repo_name_full = repo_name
@@ -289,24 +289,8 @@
 
     return _ValidRepoName
 
-def ValidForkName():
-    class _ValidForkName(formencode.validators.FancyValidator):
-        def to_python(self, value, state):
-
-            repo_name = value.get('fork_name')
-
-            slug = repo_name_slug(repo_name)
-            if slug in [ADMIN_PREFIX, '']:
-                e_dict = {'repo_name': _('This repository name is disallowed')}
-                raise formencode.Invalid('', value, state, error_dict=e_dict)
-
-            if RepoModel().get_by_repo_name(repo_name):
-                e_dict = {'fork_name':_('This repository '
-                                        'already exists')}
-                raise formencode.Invalid('', value, state,
-                                         error_dict=e_dict)
-            return value
-    return _ValidForkName
+def ValidForkName(*args, **kwargs):
+    return ValidRepoName(*args, **kwargs)
 
 
 def SlugifyName():
@@ -513,7 +497,7 @@
         else:
             password = All(UnicodeString(strip=True, min=6, not_empty=True))
             password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
-            
+
         active = StringBoolean(if_missing=False)
         name = UnicodeString(strip=True, min=1, not_empty=True)
         lastname = UnicodeString(strip=True, min=1, not_empty=True)
@@ -605,17 +589,20 @@
         chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
     return _RepoForm
 
-def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
+def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
+                 repo_groups=[]):
     class _RepoForkForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = False
-        fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
+        repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
                         SlugifyName())
+        repo_group = OneOf(repo_groups, hideList=True)
+        repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
         description = UnicodeString(strip=True, min=1, not_empty=True)
         private = StringBoolean(if_missing=False)
-        repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
-
-        chained_validators = [ValidForkName()]
+        copy_permissions = StringBoolean(if_missing=False)
+        fork_parent_id = UnicodeString()
+        chained_validators = [ValidForkName(edit, old_data)]
 
     return _RepoForkForm
 
@@ -630,7 +617,7 @@
         repo_group = OneOf(repo_groups, hideList=True)
         private = StringBoolean(if_missing=False)
 
-        chained_validators = [ValidRepoName(edit, old_data), ValidPerms, 
+        chained_validators = [ValidRepoName(edit, old_data), ValidPerms,
                               ValidSettings]
     return _RepoForm
 
--- a/rhodecode/model/notification.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/model/notification.py	Sat Nov 26 00:11:32 2011 +0200
@@ -35,8 +35,6 @@
 from rhodecode.lib import helpers as h
 from rhodecode.model import BaseModel
 from rhodecode.model.db import Notification, User, UserNotification
-from rhodecode.lib.celerylib import run_task
-from rhodecode.lib.celerylib.tasks import send_email
 
 log = logging.getLogger(__name__)
 
@@ -74,6 +72,7 @@
         :param recipients: list of int, str or User objects
         :param type_: type of notification
         """
+        from rhodecode.lib.celerylib import tasks, run_task
 
         if not getattr(recipients, '__iter__', False):
             raise Exception('recipients must be a list of iterable')
@@ -100,7 +99,7 @@
             email_body_html = EmailNotificationModel()\
                             .get_email_tmpl(type_, **{'subject':subject,
                                                       'body':h.rst(body)})
-            run_task(send_email, rec.email, email_subject, email_body,
+            run_task(tasks.send_email, rec.email, email_subject, email_body,
                      email_body_html)
 
         return notif
--- a/rhodecode/model/repo.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/model/repo.py	Sat Nov 26 00:11:32 2011 +0200
@@ -212,36 +212,33 @@
             raise
 
     def create(self, form_data, cur_user, just_db=False, fork=False):
+        from rhodecode.model.scm import ScmModel
 
         try:
             if fork:
-                repo_name = form_data['fork_name']
-                org_name = form_data['repo_name']
-                org_full_name = org_name
+                fork_parent_id = form_data['fork_parent_id']
 
-            else:
-                org_name = repo_name = form_data['repo_name']
-                repo_name_full = form_data['repo_name_full']
+            # repo name is just a name of repository
+            # while repo_name_full is a full qualified name that is combined
+            # with name and path of group
+            repo_name = form_data['repo_name']
+            repo_name_full = form_data['repo_name_full']
 
             new_repo = Repository()
             new_repo.enable_statistics = False
+
             for k, v in form_data.items():
                 if k == 'repo_name':
-                    if fork:
-                        v = repo_name
-                    else:
-                        v = repo_name_full
+                    v = repo_name_full
                 if k == 'repo_group':
                     k = 'group_id'
-
                 if k == 'description':
                     v = v or repo_name
 
                 setattr(new_repo, k, v)
 
             if fork:
-                parent_repo = self.sa.query(Repository)\
-                        .filter(Repository.repo_name == org_full_name).one()
+                parent_repo = Repository.get(fork_parent_id)
                 new_repo.fork = parent_repo
 
             new_repo.user_id = cur_user.user_id
@@ -271,19 +268,21 @@
                                    form_data['repo_group'],
                                    form_data['clone_uri'])
 
-            self.sa.commit()
-
-            #now automatically start following this repository as owner
-            from rhodecode.model.scm import ScmModel
+            # now automatically start following this repository as owner
             ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
-                                             cur_user.user_id)
+                                                    cur_user.user_id)
             return new_repo
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def create_fork(self, form_data, cur_user):
+        """
+        Simple wrapper into executing celery task for fork creation
+        
+        :param form_data:
+        :param cur_user:
+        """
         from rhodecode.lib.celerylib import tasks, run_task
         run_task(tasks.create_repo_fork, form_data, cur_user)
 
@@ -325,6 +324,11 @@
             raise
 
     def delete_stats(self, repo_name):
+        """
+        removes stats for given repo
+        
+        :param repo_name:
+        """
         try:
             obj = self.sa.query(Statistics)\
                     .filter(Statistics.repository == \
--- a/rhodecode/model/scm.py	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/model/scm.py	Sat Nov 26 00:11:32 2011 +0200
@@ -208,17 +208,14 @@
             .filter(UserFollowing.user_id == user_id).scalar()
 
         if f is not None:
-
             try:
                 self.sa.delete(f)
-                self.sa.commit()
                 action_logger(UserTemp(user_id),
                               'stopped_following_repo',
                               RepoTemp(follow_repo_id))
                 return
             except:
                 log.error(traceback.format_exc())
-                self.sa.rollback()
                 raise
 
         try:
@@ -226,13 +223,12 @@
             f.user_id = user_id
             f.follows_repo_id = follow_repo_id
             self.sa.add(f)
-            self.sa.commit()
+
             action_logger(UserTemp(user_id),
                           'started_following_repo',
                           RepoTemp(follow_repo_id))
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def toggle_following_user(self, follow_user_id, user_id):
@@ -243,11 +239,9 @@
         if f is not None:
             try:
                 self.sa.delete(f)
-                self.sa.commit()
                 return
             except:
                 log.error(traceback.format_exc())
-                self.sa.rollback()
                 raise
 
         try:
@@ -255,10 +249,8 @@
             f.user_id = user_id
             f.follows_user_id = follow_user_id
             self.sa.add(f)
-            self.sa.commit()
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def is_following_repo(self, repo_name, user_id, cache=False):
@@ -317,8 +309,8 @@
             log.error(traceback.format_exc())
             raise
 
-    def commit_change(self, repo, repo_name, cs, user, author, message, content,
-                      f_path):
+    def commit_change(self, repo, repo_name, cs, user, author, message,
+                      content, f_path):
 
         if repo.alias == 'hg':
             from vcs.backends.hg import MercurialInMemoryChangeset as IMC
--- a/rhodecode/public/css/style.css	Fri Nov 25 20:48:16 2011 +0200
+++ b/rhodecode/public/css/style.css	Sat Nov 26 00:11:32 2011 +0200
@@ -1790,6 +1790,10 @@
 	padding: 0 !important;
 }
 
+.trending_language_tbl,.trending_language_tbl tr {
+    border-spacing: 1px;
+}
+
 .trending_language {
 	background-color: #003367;
 	color: #FFF;
@@ -1797,7 +1801,7 @@
 	min-width: 20px;
 	text-decoration: none;
 	height: 12px;
-	margin-bottom: 4px;
+	margin-bottom: 0px;
 	margin-left: 5px;
 	white-space: pre;
 	padding: 3px;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/forks/fork.html	Sat Nov 26 00:11:32 2011 +0200
@@ -0,0 +1,78 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${c.repo_name} ${_('Fork')} - ${c.rhodecode_name}
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${h.link_to(u'Home',h.url('/'))}
+    &raquo;
+    ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))} 
+    &raquo;
+    ${_('fork')}
+</%def>
+
+<%def name="page_nav()">
+	${self.menu('')}
+</%def>
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}      
+    </div>
+    ${h.form(url('repo_fork_create_home',repo_name=c.repo_info.repo_name))}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+            <div class="field">
+              <div class="label">
+                  <label for="repo_name">${_('Fork name')}:</label>
+              </div>
+              <div class="input">
+                  ${h.text('repo_name',class_="small")}
+                  ${h.hidden('repo_type',c.repo_info.repo_type)}
+                  ${h.hidden('fork_parent_id',c.repo_info.repo_id)}
+              </div>
+            </div>
+            <div class="field">
+                 <div class="label">
+                     <label for="repo_group">${_('Repository group')}:</label>
+                 </div>
+                 <div class="input">
+                     ${h.select('repo_group','',c.repo_groups,class_="medium")}
+                 </div>
+            </div>                 
+            <div class="field">
+                <div class="label label-textarea">
+                    <label for="description">${_('Description')}:</label>
+                </div>
+                <div class="textarea text-area editor">
+                    ${h.textarea('description',cols=23,rows=5)}
+                </div>
+             </div>
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="private">${_('Private')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('private',value="True")}
+                </div>
+            </div>
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="private">${_('Copy permissions')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('copy_permissions',value="True")}
+                </div>
+             </div>             
+	        <div class="buttons">
+	          ${h.submit('',_('fork this repository'),class_="ui-button")}
+	        </div>                                                          
+        </div>
+    </div>    
+    ${h.end_form()}    
+</div>
+</%def>   
--- a/rhodecode/templates/settings/repo_fork.html	Fri Nov 25 20:48:16 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%def name="title()">
-    ${c.repo_name} ${_('Fork')} - ${c.rhodecode_name}
-</%def>
-
-<%def name="breadcrumbs_links()">
-    ${h.link_to(u'Home',h.url('/'))}
-    &raquo;
-    ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))} 
-    &raquo;
-    ${_('fork')}
-</%def>
-
-<%def name="page_nav()">
-	${self.menu('')}
-</%def>
-<%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}      
-    </div>
-    ${h.form(url('repo_fork_create_home',repo_name=c.repo_info.repo_name))}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-            <div class="field">
-	            <div class="label">
-	                <label for="repo_name">${_('Fork name')}:</label>
-	            </div>
-	            <div class="input">
-	                ${h.text('fork_name',class_="small")}
-	                ${h.hidden('repo_type',c.repo_info.repo_type)}
-	            </div>
-             </div>
-            <div class="field">
-                <div class="label label-textarea">
-                    <label for="description">${_('Description')}:</label>
-                </div>
-                <div class="textarea text-area editor">
-                    ${h.textarea('description',cols=23,rows=5)}
-                </div>
-             </div>
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="private">${_('Private')}:</label>
-                </div>
-                <div class="checkboxes">
-                    ${h.checkbox('private',value="True")}
-                </div>
-             </div>
-	        <div class="buttons">
-	          ${h.submit('',_('fork this repository'),class_="ui-button")}
-	        </div>                                                          
-        </div>
-    </div>    
-    ${h.end_form()}    
-</div>
-</%def>