changeset 4116:ffd45b185016 rhodecode-2.2.5-gpl

Imported some of the GPLv3'd changes from RhodeCode v2.2.5. This imports changes between changesets 21af6c4eab3d and 6177597791c2 in RhodeCode's original repository, including only changes to Python files and HTML. RhodeCode clearly licensed its changes to these files under GPLv3 in their /LICENSE file, which states the following: The Python code and integrated HTML are licensed under the GPLv3 license. (See: https://code.rhodecode.com/rhodecode/files/v2.2.5/LICENSE or http://web.archive.org/web/20140512193334/https://code.rhodecode.com/rhodecode/files/f3b123159901f15426d18e3dc395e8369f70ebe0/LICENSE for an online copy of that LICENSE file) Conservancy reviewed these changes and confirmed that they can be licensed as a whole to the Kallithea project under GPLv3-only. While some of the contents committed herein are clearly licensed GPLv3-or-later, on the whole we must assume the are GPLv3-only, since the statement above from RhodeCode indicates that they intend GPLv3-only as their license, per GPLv3ยง14 and other relevant sections of GPLv3.
author Bradley M. Kuhn <bkuhn@sfconservancy.org>
date Wed, 02 Jul 2014 19:03:13 -0400
parents 8b7294a804a0
children 6af3e67cc576
files rhodecode/__init__.py rhodecode/bin/__init__.py rhodecode/bin/base.py rhodecode/bin/ldap_sync.py rhodecode/bin/rhodecode_api.py rhodecode/bin/rhodecode_backup.py rhodecode/bin/rhodecode_config.py rhodecode/bin/rhodecode_gist.py rhodecode/config/__init__.py rhodecode/config/conf.py rhodecode/config/environment.py rhodecode/config/middleware.py rhodecode/config/post_receive_tmpl.py rhodecode/config/pre_receive_tmpl.py rhodecode/config/routing.py rhodecode/controllers/__init__.py rhodecode/controllers/admin/__init__.py rhodecode/controllers/admin/admin.py rhodecode/controllers/admin/auth_settings.py rhodecode/controllers/admin/defaults.py rhodecode/controllers/admin/gists.py rhodecode/controllers/admin/ldap_settings.py rhodecode/controllers/admin/my_account.py rhodecode/controllers/admin/notifications.py rhodecode/controllers/admin/permissions.py rhodecode/controllers/admin/repo_groups.py rhodecode/controllers/admin/repos.py rhodecode/controllers/admin/repos_groups.py rhodecode/controllers/admin/settings.py rhodecode/controllers/admin/user_groups.py rhodecode/controllers/admin/users.py rhodecode/controllers/admin/users_groups.py rhodecode/controllers/api/__init__.py rhodecode/controllers/api/api.py rhodecode/controllers/bookmarks.py rhodecode/controllers/branches.py rhodecode/controllers/changelog.py rhodecode/controllers/changeset.py rhodecode/controllers/compare.py rhodecode/controllers/error.py rhodecode/controllers/feed.py rhodecode/controllers/files.py rhodecode/controllers/followers.py rhodecode/controllers/forks.py rhodecode/controllers/home.py rhodecode/controllers/journal.py rhodecode/controllers/login.py rhodecode/controllers/pullrequests.py rhodecode/controllers/search.py rhodecode/controllers/summary.py rhodecode/controllers/tags.py rhodecode/lib/__init__.py rhodecode/lib/annotate.py rhodecode/lib/app_globals.py rhodecode/lib/auth.py rhodecode/lib/auth_ldap.py rhodecode/lib/auth_modules/__init__.py rhodecode/lib/auth_modules/auth_container.py rhodecode/lib/auth_modules/auth_crowd.py rhodecode/lib/auth_modules/auth_ldap.py rhodecode/lib/auth_modules/auth_pam.py rhodecode/lib/auth_modules/auth_rhodecode.py rhodecode/lib/base.py rhodecode/lib/celerylib/__init__.py rhodecode/lib/celerylib/tasks.py rhodecode/lib/celerypylons/__init__.py rhodecode/lib/celerypylons/commands.py rhodecode/lib/celerypylons/loader.py rhodecode/lib/colored_formatter.py rhodecode/lib/compat.py rhodecode/lib/db_manage.py rhodecode/lib/dbmigrate/__init__.py rhodecode/lib/dbmigrate/schema/__init__.py rhodecode/lib/dbmigrate/schema/db_1_2_0.py rhodecode/lib/dbmigrate/schema/db_1_3_0.py rhodecode/lib/dbmigrate/schema/db_1_4_0.py rhodecode/lib/dbmigrate/schema/db_1_5_0.py rhodecode/lib/dbmigrate/schema/db_1_5_2.py rhodecode/lib/dbmigrate/schema/db_1_6_0.py rhodecode/lib/dbmigrate/schema/db_1_7_0.py rhodecode/lib/dbmigrate/schema/db_1_8_0.py rhodecode/lib/dbmigrate/schema/db_2_0_0.py rhodecode/lib/dbmigrate/schema/db_2_0_1.py rhodecode/lib/dbmigrate/schema/db_2_0_2.py rhodecode/lib/dbmigrate/schema/db_2_1_0.py rhodecode/lib/dbmigrate/schema/db_2_2_0.py rhodecode/lib/dbmigrate/schema/db_2_2_3.py rhodecode/lib/dbmigrate/versions/008_version_1_5_0.py rhodecode/lib/dbmigrate/versions/010_version_1_5_2.py rhodecode/lib/dbmigrate/versions/011_version_1_6_0.py rhodecode/lib/dbmigrate/versions/012_version_1_7_0.py rhodecode/lib/dbmigrate/versions/014_version_1_7_1.py rhodecode/lib/dbmigrate/versions/015_version_1_8_0.py rhodecode/lib/dbmigrate/versions/016_version_2_0_0.py rhodecode/lib/dbmigrate/versions/017_version_2_0_0.py rhodecode/lib/dbmigrate/versions/018_version_2_0_0.py rhodecode/lib/dbmigrate/versions/019_version_2_0_0.py rhodecode/lib/dbmigrate/versions/020_version_2_0_1.py rhodecode/lib/dbmigrate/versions/021_version_2_0_2.py rhodecode/lib/dbmigrate/versions/022_version_2_0_2.py rhodecode/lib/dbmigrate/versions/023_version_2_1_0.py rhodecode/lib/dbmigrate/versions/024_version_2_1_0.py rhodecode/lib/dbmigrate/versions/025_version_2_1_0.py rhodecode/lib/dbmigrate/versions/026_version_2_2_0.py rhodecode/lib/dbmigrate/versions/027_version_2_2_0.py rhodecode/lib/dbmigrate/versions/028_version_2_2_3.py rhodecode/lib/dbmigrate/versions/029_version_2_2_3.py rhodecode/lib/dbmigrate/versions/030_version_2_2_3.py rhodecode/lib/dbmigrate/versions/031_version_2_2_3.py rhodecode/lib/dbmigrate/versions/__init__.py rhodecode/lib/diffs.py rhodecode/lib/exceptions.py rhodecode/lib/graphmod.py rhodecode/lib/helpers.py rhodecode/lib/hooks.py rhodecode/lib/indexers/__init__.py rhodecode/lib/indexers/daemon.py rhodecode/lib/markup_renderer.py rhodecode/lib/middleware/__init__.py rhodecode/lib/middleware/errormator.py rhodecode/lib/middleware/https_fixup.py rhodecode/lib/middleware/pygrack.py rhodecode/lib/middleware/sentry.py rhodecode/lib/middleware/simplegit.py rhodecode/lib/middleware/simplehg.py rhodecode/lib/middleware/wrapper.py rhodecode/lib/paster_commands/__init__.py rhodecode/lib/paster_commands/cache_keys.py rhodecode/lib/paster_commands/cleanup.py rhodecode/lib/paster_commands/ishell.py rhodecode/lib/paster_commands/make_index.py rhodecode/lib/paster_commands/make_rcextensions.py rhodecode/lib/paster_commands/repo_scan.py rhodecode/lib/paster_commands/update_repoinfo.py rhodecode/lib/pidlock.py rhodecode/lib/rcmail/smtp_mailer.py rhodecode/lib/recaptcha.py rhodecode/lib/timerproxy.py rhodecode/lib/utils.py rhodecode/lib/utils2.py rhodecode/lib/vcs/backends/base.py rhodecode/lib/vcs/backends/git/changeset.py rhodecode/lib/vcs/backends/git/config.py rhodecode/lib/vcs/backends/git/inmemory.py rhodecode/lib/vcs/backends/git/repository.py rhodecode/lib/vcs/backends/hg/repository.py rhodecode/lib/vcs/nodes.py rhodecode/lib/vcs/subprocessio.py rhodecode/lib/verlib.py rhodecode/model/__init__.py rhodecode/model/api_key.py rhodecode/model/changeset_status.py rhodecode/model/comment.py rhodecode/model/db.py rhodecode/model/forms.py rhodecode/model/gist.py rhodecode/model/license.py rhodecode/model/meta.py rhodecode/model/notification.py rhodecode/model/permission.py rhodecode/model/pull_request.py rhodecode/model/repo.py rhodecode/model/repo_group.py rhodecode/model/repo_permission.py rhodecode/model/repos_group.py rhodecode/model/scm.py rhodecode/model/user.py rhodecode/model/user_group.py rhodecode/model/users_group.py rhodecode/model/validators.py rhodecode/templates/admin/admin.html rhodecode/templates/admin/auth/auth_settings.html rhodecode/templates/admin/defaults/defaults.html rhodecode/templates/admin/gists/edit.html rhodecode/templates/admin/gists/index.html rhodecode/templates/admin/gists/new.html rhodecode/templates/admin/gists/show.html rhodecode/templates/admin/ldap/ldap.html rhodecode/templates/admin/my_account/my_account.html rhodecode/templates/admin/my_account/my_account_api_keys.html rhodecode/templates/admin/my_account/my_account_emails.html rhodecode/templates/admin/my_account/my_account_password.html rhodecode/templates/admin/my_account/my_account_perms.html rhodecode/templates/admin/my_account/my_account_profile.html rhodecode/templates/admin/my_account/my_account_pullrequests.html rhodecode/templates/admin/my_account/my_account_repos.html rhodecode/templates/admin/my_account/my_account_watched.html rhodecode/templates/admin/notifications/notifications.html rhodecode/templates/admin/notifications/notifications_data.html rhodecode/templates/admin/notifications/show_notification.html rhodecode/templates/admin/permissions/permissions.html rhodecode/templates/admin/permissions/permissions_globals.html rhodecode/templates/admin/permissions/permissions_ips.html rhodecode/templates/admin/permissions/permissions_perms.html rhodecode/templates/admin/repo_groups/repo_group_add.html rhodecode/templates/admin/repo_groups/repo_group_edit.html rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html rhodecode/templates/admin/repo_groups/repo_group_show.html rhodecode/templates/admin/repo_groups/repo_groups.html rhodecode/templates/admin/repos/repo_add.html rhodecode/templates/admin/repos/repo_add_base.html rhodecode/templates/admin/repos/repo_creating.html rhodecode/templates/admin/repos/repo_edit.html rhodecode/templates/admin/repos/repo_edit_advanced.html rhodecode/templates/admin/repos/repo_edit_caches.html rhodecode/templates/admin/repos/repo_edit_fields.html rhodecode/templates/admin/repos/repo_edit_fork.html rhodecode/templates/admin/repos/repo_edit_permissions.html rhodecode/templates/admin/repos/repo_edit_perms.html rhodecode/templates/admin/repos/repo_edit_remote.html rhodecode/templates/admin/repos/repo_edit_settings.html rhodecode/templates/admin/repos/repo_edit_statistics.html rhodecode/templates/admin/repos/repos.html rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html rhodecode/templates/admin/repos_groups/repos_groups.html rhodecode/templates/admin/repos_groups/repos_groups_add.html rhodecode/templates/admin/repos_groups/repos_groups_edit.html rhodecode/templates/admin/repos_groups/repos_groups_show.html rhodecode/templates/admin/settings/hooks.html rhodecode/templates/admin/settings/settings.html rhodecode/templates/admin/settings/settings_email.html rhodecode/templates/admin/settings/settings_global.html rhodecode/templates/admin/settings/settings_hooks.html rhodecode/templates/admin/settings/settings_license.html rhodecode/templates/admin/settings/settings_mapping.html rhodecode/templates/admin/settings/settings_search.html rhodecode/templates/admin/settings/settings_system.html rhodecode/templates/admin/settings/settings_system_update.html rhodecode/templates/admin/settings/settings_vcs.html rhodecode/templates/admin/settings/settings_visual.html rhodecode/templates/admin/user_groups/user_group_add.html rhodecode/templates/admin/user_groups/user_group_edit.html rhodecode/templates/admin/user_groups/user_group_edit_advanced.html rhodecode/templates/admin/user_groups/user_group_edit_default_perms.html rhodecode/templates/admin/user_groups/user_group_edit_members.html rhodecode/templates/admin/user_groups/user_group_edit_perms.html rhodecode/templates/admin/user_groups/user_group_edit_settings.html rhodecode/templates/admin/user_groups/user_groups.html rhodecode/templates/admin/users/user_add.html rhodecode/templates/admin/users/user_edit.html rhodecode/templates/admin/users/user_edit_advanced.html rhodecode/templates/admin/users/user_edit_api_keys.html rhodecode/templates/admin/users/user_edit_emails.html rhodecode/templates/admin/users/user_edit_ips.html rhodecode/templates/admin/users/user_edit_my_account.html rhodecode/templates/admin/users/user_edit_my_account_form.html rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html rhodecode/templates/admin/users/user_edit_my_account_repos.html rhodecode/templates/admin/users/user_edit_perms.html rhodecode/templates/admin/users/user_edit_profile.html rhodecode/templates/admin/users/users.html rhodecode/templates/admin/users_groups/user_group_edit_perms.html rhodecode/templates/admin/users_groups/users_group_add.html rhodecode/templates/admin/users_groups/users_group_edit.html rhodecode/templates/admin/users_groups/users_groups.html rhodecode/templates/base/base.html rhodecode/templates/base/default_perms_box.html rhodecode/templates/base/flash_msg.html rhodecode/templates/base/perms_summary.html rhodecode/templates/base/root.html rhodecode/templates/bookmarks/bookmarks.html rhodecode/templates/bookmarks/bookmarks_data.html rhodecode/templates/branches/branches.html rhodecode/templates/branches/branches_data.html rhodecode/templates/changelog/changelog.html rhodecode/templates/changelog/changelog_summary_data.html rhodecode/templates/changeset/changeset.html rhodecode/templates/changeset/changeset_file_comment.html rhodecode/templates/changeset/changeset_range.html rhodecode/templates/changeset/diff_block.html rhodecode/templates/compare/compare_cs.html rhodecode/templates/compare/compare_diff.html rhodecode/templates/data_table/_dt_elements.html rhodecode/templates/errors/error_document.html rhodecode/templates/files/diff_2way.html rhodecode/templates/files/file_diff.html rhodecode/templates/files/files.html rhodecode/templates/files/files_add.html rhodecode/templates/files/files_browser.html rhodecode/templates/files/files_delete.html rhodecode/templates/files/files_edit.html rhodecode/templates/files/files_history_box.html rhodecode/templates/files/files_source.html rhodecode/templates/files/files_ypjax.html rhodecode/templates/followers/followers.html rhodecode/templates/forks/fork.html rhodecode/templates/forks/forks.html rhodecode/templates/forks/forks_data.html rhodecode/templates/index.html rhodecode/templates/index_base.html rhodecode/templates/journal/journal.html rhodecode/templates/journal/public_journal.html rhodecode/templates/login.html rhodecode/templates/password_reset.html rhodecode/templates/pullrequests/pullrequest.html rhodecode/templates/pullrequests/pullrequest_show.html rhodecode/templates/pullrequests/pullrequest_show_all.html rhodecode/templates/register.html rhodecode/templates/repo_switcher_list.html rhodecode/templates/search/search.html rhodecode/templates/search/search_commit.html rhodecode/templates/summary/statistics.html rhodecode/templates/summary/summary.html rhodecode/templates/switch_to_list.html rhodecode/templates/tags/tags.html rhodecode/templates/tags/tags_data.html rhodecode/tests/__init__.py rhodecode/tests/api/__init__.py rhodecode/tests/api/api_base.py rhodecode/tests/api/test_api_git.py rhodecode/tests/api/test_api_hg.py rhodecode/tests/fixture.py rhodecode/tests/fixtures/git_node_history_response.json rhodecode/tests/fixtures/hg_node_history_response.json rhodecode/tests/functional/test_admin_auth_settings.py rhodecode/tests/functional/test_admin_defaults.py rhodecode/tests/functional/test_admin_gists.py rhodecode/tests/functional/test_admin_ldap_settings.py rhodecode/tests/functional/test_admin_notifications.py rhodecode/tests/functional/test_admin_permissions.py rhodecode/tests/functional/test_admin_repo_groups.py rhodecode/tests/functional/test_admin_repos.py rhodecode/tests/functional/test_admin_repos_groups.py rhodecode/tests/functional/test_admin_settings.py rhodecode/tests/functional/test_admin_user_groups.py rhodecode/tests/functional/test_admin_users.py rhodecode/tests/functional/test_admin_users_groups.py rhodecode/tests/functional/test_compare.py rhodecode/tests/functional/test_compare_local.py rhodecode/tests/functional/test_files.py rhodecode/tests/functional/test_forks.py rhodecode/tests/functional/test_home.py rhodecode/tests/functional/test_login.py rhodecode/tests/functional/test_my_account.py rhodecode/tests/functional/test_repo_groups.py rhodecode/tests/functional/test_repos_groups.py rhodecode/tests/functional/test_summary.py rhodecode/tests/models/common.py rhodecode/tests/models/test_diff_parsers.py rhodecode/tests/models/test_license.py rhodecode/tests/models/test_permissions.py rhodecode/tests/models/test_repo_groups.py rhodecode/tests/models/test_repos_groups.py rhodecode/tests/models/test_user_group_permissions_on_repo_groups.py rhodecode/tests/models/test_user_groups.py rhodecode/tests/models/test_user_permissions_on_groups.py rhodecode/tests/models/test_user_permissions_on_repo_groups.py rhodecode/tests/models/test_users.py rhodecode/tests/models/test_users_group_permissions_on_groups.py rhodecode/tests/other/test_libs.py rhodecode/tests/other/test_validators.py rhodecode/tests/other/test_vcs_operations.py rhodecode/tests/scripts/create_rc.sh rhodecode/tests/scripts/test_concurency.py rhodecode/tests/scripts/test_crawler.py rhodecode/tests/vcs/test_changesets.py rhodecode/websetup.py setup.py
diffstat 359 files changed, 41041 insertions(+), 13352 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.__init__
-    ~~~~~~~~~~~~~~~~~~
-
-    RhodeCode, a web based repository management based on pylons
-    versioning implementation: http://www.python.org/dev/peps/pep-0386/
-
-    :created_on: Apr 9, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,10 +11,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/>.
+"""
+rhodecode.__init__
+~~~~~~~~~~~~~~~~~~
+
+RhodeCode, a web based repository management based on pylons
+versioning implementation: http://www.python.org/dev/peps/pep-0386/
+
+:created_on: Apr 9, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import sys
 import platform
 
-VERSION = (1, 7, 1)
+VERSION = (2, 2, 5)
 BACKENDS = {
     'hg': 'Mercurial repository',
     'git': 'Git repository',
@@ -49,14 +50,21 @@
 except ImportError:
     pass
 
-__version__ = ('.'.join((str(each) for each in VERSION[:3])) +
-               '.'.join(VERSION[3:]))
-__dbversion__ = 14  # defines current db version for migrations
+__version__ = ('.'.join((str(each) for each in VERSION[:3])))
+__dbversion__ = 31  # defines current db version for migrations
 __platform__ = platform.system()
 __license__ = 'GPLv3'
 __py_version__ = sys.version_info
-__author__ = 'Marcin Kuzminski'
-__url__ = 'http://rhodecode.org'
+__author__ = 'RhodeCode GmbH'
+__url__ = 'http://rhodecode.com'
 
 is_windows = __platform__ in ['Windows']
 is_unix = not is_windows
+
+if len(VERSION) > 3:
+    __version__ += '.'+VERSION[3]
+
+    if len(VERSION) > 4:
+        __version__ += VERSION[4]
+    else:
+        __version__ += '0'
--- a/rhodecode/bin/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/bin/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.bin.__init__
+~~~~~~~~~~~~~~~~~~~~~~
+
+Binary scripts for RhodeCode
+
+:created_on: Jun 03, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
--- a/rhodecode/bin/base.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/bin/base.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,6 +1,28 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
+rhodecode.bin.base
+~~~~~~~~~~~~~~~~~~
+
 Base utils for shell scripts
+
+:created_on: May 09, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
 """
+
 import os
 import sys
 import random
@@ -46,7 +68,7 @@
 
     if not method:
         raise Exception('please specify method name !')
-
+    apihost = apihost.rstrip('/')
     id_ = random.randrange(1, 9999)
     req = urllib2.Request('%s/_admin/api' % apihost,
                       data=json.dumps(_build_data(id_)),
--- a/rhodecode/bin/ldap_sync.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/bin/ldap_sync.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -10,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.bin.__init__
+~~~~~~~~~~~~~~~~~~~~~~
+
+LDAP sync script
+
+:created_on: Mar 06, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import ldap
 import urllib2
@@ -45,7 +57,7 @@
     """ User is not a member of the target group. """
 
 
-class RhodecodeAPI():
+class RhodecodeAPI(object):
 
     def __init__(self, url, key):
         self.url = url
@@ -90,7 +102,7 @@
             "group_name": name,
             "active": str(active)
         }
-        self.rhodecode_api_post("create_users_group", args)
+        self.rhodecode_api_post("create_user_group", args)
 
     def add_membership(self, group, username):
         """Add specific user to a group."""
@@ -98,7 +110,7 @@
             "usersgroupid": group,
             "userid": username
         }
-        result = self.rhodecode_api_post("add_user_to_users_group", args)
+        result = self.rhodecode_api_post("add_user_to_user_group", args)
         if not result["success"]:
             raise UserAlreadyInGroupError("User %s already in group %s." %
                                           (username, group))
@@ -109,7 +121,7 @@
             "usersgroupid": group,
             "userid": username
         }
-        result = self.rhodecode_api_post("remove_user_from_users_group", args)
+        result = self.rhodecode_api_post("remove_user_from_user_group", args)
         if not result["success"]:
             raise UserNotInGroupError("User %s not in group %s." %
                                       (username, group))
@@ -117,7 +129,7 @@
     def get_group_members(self, name):
         """Get the list of member usernames from a user group."""
         args = {"usersgroupid": name}
-        members = self.rhodecode_api_post("get_users_group", args)['members']
+        members = self.rhodecode_api_post("get_user_group", args)['members']
         member_list = []
         for member in members:
             member_list.append(member["username"])
@@ -126,7 +138,7 @@
     def get_group(self, name):
         """Return group info."""
         args = {"usersgroupid": name}
-        return self.rhodecode_api_post("get_users_group", args)
+        return self.rhodecode_api_post("get_user_group", args)
 
     def get_user(self, username):
         """Return user info."""
@@ -134,7 +146,7 @@
         return self.rhodecode_api_post("get_user", args)
 
 
-class LdapClient():
+class LdapClient(object):
 
     def __init__(self, uri, user, key, base_dn):
         self.client = ldap.initialize(uri, trace_level=0)
@@ -199,7 +211,7 @@
         groups = self.ldap_client.get_groups()
         for group in groups:
             try:
-                self.rhodecode_api.create_group(group)
+                self.rhodecode_api.create_repo_group(group)
                 added += 1
             except Exception:
                 existing += 1
--- a/rhodecode/bin/rhodecode_api.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/bin/rhodecode_api.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.bin.api
-    ~~~~~~~~~~~~~~~~~
-
-    Api CLI client for RhodeCode
-
-    :created_on: Jun 3, 2012
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.bin.api
+~~~~~~~~~~~~~~~~~
+
+Api CLI client for RhodeCode
+
+:created_on: Jun 3, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 from __future__ import with_statement
 import sys
@@ -35,7 +35,7 @@
       "rhodecode-api [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] "
       "[--config=CONFIG] [--save-config] "
       "METHOD <key:val> <key2:val> ...\n"
-      "Create config file: rhodecode-gist --apikey=<key> --apihost=http://rhodecode.server --save-config"
+      "Create config file: rhodecode-api --apikey=<key> --apihost=http://rhodecode.server --save-config"
     )
 
     parser = argparse.ArgumentParser(description='RhodeCode API cli',
@@ -106,15 +106,18 @@
         print 'Calling method %s => %s' % (method, apihost)
 
     json_resp = api_call(apikey, apihost, method, **margs)
+    error_prefix = ''
     if json_resp['error']:
+        error_prefix = 'ERROR:'
         json_data = json_resp['error']
     else:
         json_data = json_resp['result']
     if args.format == FORMAT_JSON:
         print json.dumps(json_data)
     elif args.format == FORMAT_PRETTY:
-        print 'Server response \n%s' % (
-                json.dumps(json_data, indent=4, sort_keys=True))
+        print 'Server response \n%s%s' % (
+            error_prefix, json.dumps(json_data, indent=4, sort_keys=True)
+        )
     return 0
 
 if __name__ == '__main__':
--- a/rhodecode/bin/rhodecode_backup.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/bin/rhodecode_backup.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.bin.backup_manager
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Repositories backup manager, it allows to backups all
-    repositories and send it to backup server using RSA key via ssh.
-
-    :created_on: Feb 28, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.bin.backup_manager
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Repositories backup manager, it allows to backups all
+repositories and send it to backup server using RSA key via ssh.
+
+:created_on: Feb 28, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import os
 import sys
--- a/rhodecode/bin/rhodecode_config.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/bin/rhodecode_config.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,7 +1,29 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
-config generator
+rhodecode.bin.rhodecode_config
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+configuration generator for RhodeCode
 
+:created_on: Jun 18, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
 """
+
+
 from __future__ import with_statement
 import os
 import sys
@@ -71,13 +93,16 @@
         print parser.print_help()
         sys.exit(0)
     # defaults that can be overwritten by arguments
+    from rhodecode.model.license import LicenseModel
+    license_token = LicenseModel.generate_license_token()
     tmpl_stored_args = {
         'http_server': 'waitress',
         'lang': 'en',
         'database_engine': 'sqlite',
         'host': '127.0.0.1',
         'port': 5000,
-        'error_aggregation_service': None
+        'error_aggregation_service': None,
+        'license_token': license_token
     }
     if other:
         # parse arguments, we assume only first is correct
--- a/rhodecode/bin/rhodecode_gist.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/bin/rhodecode_gist.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.bin.gist
-    ~~~~~~~~~~~~~~~~~~
-
-    Gist CLI client for RhodeCode
-
-    :created_on: May 9, 2013
-    :author: marcink
-    :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.bin.rhodecode_gist
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Gist CLI client for RhodeCode
+
+:created_on: May 9, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 from __future__ import with_statement
 import os
--- a/rhodecode/config/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/config/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
--- a/rhodecode/config/conf.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/config/conf.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,14 +1,26 @@
 # -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
-    package.rhodecode.config.conf
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+rhodecode.config.conf
+~~~~~~~~~~~~~~~~~~~~~
 
-    Various config settings for RhodeCode
+Various config settings for RhodeCode
 
-    :created_on: Mar 7, 2012
-    :author: marcink
-    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
-    :license: <name>, see LICENSE_FILE for more details.
+:created_on: Mar 7, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
 """
 from rhodecode import EXTENSIONS
 
--- a/rhodecode/config/environment.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/config/environment.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,8 +1,24 @@
-"""Pylons environment configuration"""
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+    Pylons environment configuration
+"""
 
 import os
 import logging
 import rhodecode
+import platform
 
 from mako.lookup import TemplateLookup
 from pylons.configuration import PylonsConfig
@@ -27,7 +43,8 @@
 log = logging.getLogger(__name__)
 
 
-def load_environment(global_conf, app_conf, initial=False):
+def load_environment(global_conf, app_conf, initial=False,
+                     test_env=None, test_index=None):
     """
     Configure the Pylons environment via the ``pylons.config``
     object
@@ -73,18 +90,22 @@
     config['pylons.strict_tmpl_context'] = True
     test = os.path.split(config['__file__'])[-1] == 'test.ini'
     if test:
+        if test_env is None:
+            test_env = not int(os.environ.get('RC_NO_TMP_PATH', 0))
+        if test_index is None:
+            test_index = not int(os.environ.get('RC_WHOOSH_TEST_DISABLE', 0))
         if os.environ.get('TEST_DB'):
             # swap config if we pass enviroment variable
             config['sqlalchemy.db1.url'] = os.environ.get('TEST_DB')
 
         from rhodecode.lib.utils import create_test_env, create_test_index
-        from rhodecode.tests import  TESTS_TMP_PATH
-        # set RC_NO_TMP_PATH=1 to disable re-creating the database and
-        # test repos
-        if not int(os.environ.get('RC_NO_TMP_PATH', 0)):
+        from rhodecode.tests import TESTS_TMP_PATH
+        #set RC_NO_TMP_PATH=1 to disable re-creating the database and
+        #test repos
+        if test_env:
             create_test_env(TESTS_TMP_PATH, config)
-        # set RC_WHOOSH_TEST_DISABLE=1 to disable whoosh index during tests
-        if not int(os.environ.get('RC_WHOOSH_TEST_DISABLE', 0)):
+        #set RC_WHOOSH_TEST_DISABLE=1 to disable whoosh index during tests
+        if test_index:
             create_test_index(TESTS_TMP_PATH, config, True)
 
     DbManage.check_waitress()
@@ -100,7 +121,7 @@
 
     instance_id = rhodecode.CONFIG.get('instance_id')
     if instance_id == '*':
-        instance_id = '%s-%s' % (os.uname()[1], os.getpid())
+        instance_id = '%s-%s' % (platform.uname()[1], os.getpid())
         rhodecode.CONFIG['instance_id'] = instance_id
 
     # CONFIGURATION OPTIONS HERE (note: all config options will override
--- a/rhodecode/config/middleware.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/config/middleware.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,4 +1,19 @@
-"""Pylons middleware initialization"""
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+    Pylons middleware initialization
+"""
 
 from beaker.middleware import SessionMiddleware
 from routes.middleware import RoutesMiddleware
--- a/rhodecode/config/post_receive_tmpl.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/config/post_receive_tmpl.py	Wed Jul 02 19:03:13 2014 -0400
@@ -6,8 +6,11 @@
     import rhodecode
     RC_HOOK_VER = '_TMPL_'
     os.environ['RC_HOOK_VER'] = RC_HOOK_VER
-    from rhodecode.lib.hooks import handle_git_post_receive
+    from rhodecode.lib.hooks import handle_git_post_receive as _handler
 except ImportError:
+    if os.environ.get('RC_DEBUG_GIT_HOOK'):
+        import traceback
+        print traceback.format_exc()
     rhodecode = None
 
 
@@ -24,7 +27,7 @@
     # runs git and later git executes this hook.
     # Environ gets some additional info from rhodecode system
     # like IP or username from basic-auth
-    handle_git_post_receive(repo_path, push_data, os.environ)
+    _handler(repo_path, push_data, os.environ)
     sys.exit(0)
 
 if __name__ == '__main__':
--- a/rhodecode/config/pre_receive_tmpl.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/config/pre_receive_tmpl.py	Wed Jul 02 19:03:13 2014 -0400
@@ -6,8 +6,11 @@
     import rhodecode
     RC_HOOK_VER = '_TMPL_'
     os.environ['RC_HOOK_VER'] = RC_HOOK_VER
-    from rhodecode.lib.hooks import handle_git_pre_receive
+    from rhodecode.lib.hooks import handle_git_pre_receive as _handler
 except ImportError:
+    if os.environ.get('RC_DEBUG_GIT_HOOK'):
+        import traceback
+        print traceback.format_exc()
     rhodecode = None
 
 
@@ -24,7 +27,7 @@
     # runs git and later git executes this hook.
     # Environ gets some additional info from rhodecode system
     # like IP or username from basic-auth
-    handle_git_pre_receive(repo_path, push_data, os.environ)
+    _handler(repo_path, push_data, os.environ)
     sys.exit(0)
 
 if __name__ == '__main__':
--- a/rhodecode/config/routing.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/config/routing.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,16 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 Routes configuration
 
@@ -5,6 +18,7 @@
 may take precedent over the more generic routes. For more information
 refer to the routes manual at http://routes.groovie.org/docs/
 """
+
 from __future__ import with_statement
 from routes import Mapper
 
@@ -15,12 +29,12 @@
 def make_map(config):
     """Create, configure and return the routes Mapper"""
     rmap = Mapper(directory=config['pylons.paths']['controllers'],
-                 always_scan=config['debug'])
+                  always_scan=config['debug'])
     rmap.minimization = False
     rmap.explicit = False
 
-    from rhodecode.lib.utils import is_valid_repo
-    from rhodecode.lib.utils import is_valid_repos_group
+    from rhodecode.lib.utils import (is_valid_repo, is_valid_repo_group,
+                                     get_repo_by_id)
 
     def check_repo(environ, match_dict):
         """
@@ -29,20 +43,16 @@
         :param environ:
         :param match_dict:
         """
-        from rhodecode.model.db import Repository
         repo_name = match_dict.get('repo_name')
 
         if match_dict.get('f_path'):
             #fix for multiple initial slashes that causes errors
             match_dict['f_path'] = match_dict['f_path'].lstrip('/')
 
-        try:
-            by_id = repo_name.split('_')
-            if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
-                repo_name = Repository.get(by_id[1]).repo_name
-                match_dict['repo_name'] = repo_name
-        except Exception:
-            pass
+        by_id_match = get_repo_by_id(repo_name)
+        if by_id_match:
+            repo_name = by_id_match
+            match_dict['repo_name'] = repo_name
 
         return is_valid_repo(repo_name, config['base_path'])
 
@@ -53,8 +63,8 @@
         :param environ:
         :param match_dict:
         """
-        repos_group_name = match_dict.get('group_name')
-        return is_valid_repos_group(repos_group_name, config['base_path'])
+        repo_group_name = match_dict.get('group_name')
+        return is_valid_repo_group(repo_group_name, config['base_path'])
 
     def check_group_skip_path(environ, match_dict):
         """
@@ -64,9 +74,9 @@
         :param environ:
         :param match_dict:
         """
-        repos_group_name = match_dict.get('group_name')
-        return is_valid_repos_group(repos_group_name, config['base_path'],
-                                    skip_path_check=True)
+        repo_group_name = match_dict.get('group_name')
+        return is_valid_repo_group(repo_group_name, config['base_path'],
+                                   skip_path_check=True)
 
     def check_user_group(environ, match_dict):
         """
@@ -91,131 +101,82 @@
 
     #MAIN PAGE
     rmap.connect('home', '/', controller='home', action='index')
-    rmap.connect('repo_switcher', '/repos', controller='home',
-                 action='repo_switcher')
-    rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
-                 controller='home', action='branch_tag_switcher')
+    rmap.connect('repo_switcher_data', '/_repos', controller='home',
+                 action='repo_switcher_data')
+
     rmap.connect('rst_help',
                  "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
                  _static=True)
-    rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
+    rmap.connect('rhodecode_official', "https://rhodecode.com", _static=True)
+    rmap.connect('rc_issue_tracker', 'https://rhodecode.com/help/', _static=True)
 
-    #ADMIN REPOSITORY REST ROUTES
+    #ADMIN REPOSITORY ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/repos') as m:
         m.connect("repos", "/repos",
-             action="create", conditions=dict(method=["POST"]))
+                  action="create", conditions=dict(method=["POST"]))
         m.connect("repos", "/repos",
-             action="index", conditions=dict(method=["GET"]))
-        m.connect("formatted_repos", "/repos.{format}",
-             action="index",
-            conditions=dict(method=["GET"]))
+                  action="index", conditions=dict(method=["GET"]))
         m.connect("new_repo", "/create_repository",
                   action="create_repository", conditions=dict(method=["GET"]))
         m.connect("/repos/{repo_name:.*?}",
-             action="update", conditions=dict(method=["PUT"],
-                                              function=check_repo))
-        m.connect("/repos/{repo_name:.*?}",
-             action="delete", conditions=dict(method=["DELETE"],
-                                              function=check_repo))
-        m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
-             action="edit", conditions=dict(method=["GET"],
-                                            function=check_repo))
+                  action="update", conditions=dict(method=["PUT"],
+                  function=check_repo))
+        m.connect("delete_repo", "/repos/{repo_name:.*?}",
+                  action="delete", conditions=dict(method=["DELETE"],
+                  ))
         m.connect("repo", "/repos/{repo_name:.*?}",
-             action="show", conditions=dict(method=["GET"],
-                                            function=check_repo))
-        m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
-             action="show", conditions=dict(method=["GET"],
-                                            function=check_repo))
-        #add repo perm member
-        m.connect('set_repo_perm_member',
-                  "/repos/{repo_name:.*?}/grant_perm",
-                  action="set_repo_perm_member",
-                  conditions=dict(method=["POST"], function=check_repo))
-
-        #ajax delete repo perm user
-        m.connect('delete_repo_perm_member',
-                  "/repos/{repo_name:.*?}/revoke_perm",
-                  action="delete_repo_perm_member",
-                  conditions=dict(method=["DELETE"], function=check_repo))
+                  action="show", conditions=dict(method=["GET"],
+                  function=check_repo))
 
-        #settings actions
-        m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
-                  action="repo_stats", conditions=dict(method=["DELETE"],
-                                                       function=check_repo))
-        m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
-                  action="repo_cache", conditions=dict(method=["DELETE"],
-                                                       function=check_repo))
-        m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
-                  action="repo_public_journal", conditions=dict(method=["PUT"],
-                                                        function=check_repo))
-        m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
-                  action="repo_pull", conditions=dict(method=["PUT"],
-                                                      function=check_repo))
-        m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
-                  action="repo_as_fork", conditions=dict(method=["PUT"],
-                                                      function=check_repo))
-        m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
-                  action="repo_locking", conditions=dict(method=["PUT"],
-                                                      function=check_repo))
-        m.connect('toggle_locking', "/locking_toggle/{repo_name:.*?}",
-                  action="toggle_locking", conditions=dict(method=["GET"],
-                                                      function=check_repo))
-
-        #repo fields
-        m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
-                  action="create_repo_field", conditions=dict(method=["PUT"],
-                                                      function=check_repo))
-
-        m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
-                  action="delete_repo_field", conditions=dict(method=["DELETE"],
-                                                      function=check_repo))
-
+    #ADMIN REPOSITORY GROUPS ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
-                        controller='admin/repos_groups') as m:
-        m.connect("repos_groups", "/repos_groups",
+                        controller='admin/repo_groups') as m:
+        m.connect("repos_groups", "/repo_groups",
                   action="create", conditions=dict(method=["POST"]))
-        m.connect("repos_groups", "/repos_groups",
+        m.connect("repos_groups", "/repo_groups",
                   action="index", conditions=dict(method=["GET"]))
-        m.connect("formatted_repos_groups", "/repos_groups.{format}",
-                  action="index", conditions=dict(method=["GET"]))
-        m.connect("new_repos_group", "/repos_groups/new",
+        m.connect("new_repos_group", "/repo_groups/new",
                   action="new", conditions=dict(method=["GET"]))
-        m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
-                  action="new", conditions=dict(method=["GET"]))
-        m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
+        m.connect("update_repos_group", "/repo_groups/{group_name:.*?}",
                   action="update", conditions=dict(method=["PUT"],
                                                    function=check_group))
-        #add repo group perm member
-        m.connect('set_repo_group_perm_member',
-                  "/repos_groups/{group_name:.*?}/grant_perm",
-                  action="set_repo_group_perm_member",
-                  conditions=dict(method=["POST"], function=check_group))
 
-        #ajax delete repo group perm
-        m.connect('delete_repo_group_perm_member',
-                  "/repos_groups/{group_name:.*?}/revoke_perm",
-                  action="delete_repo_group_perm_member",
-                  conditions=dict(method=["DELETE"], function=check_group))
-
-        m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
-                  action="delete", conditions=dict(method=["DELETE"],
-                                                   function=check_group_skip_path))
-        m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
-                  action="edit", conditions=dict(method=["GET"],
-                                                 function=check_group))
-        m.connect("formatted_edit_repos_group",
-                  "/repos_groups/{group_name:.*?}.{format}/edit",
-                  action="edit", conditions=dict(method=["GET"],
-                                                 function=check_group))
-        m.connect("repos_group", "/repos_groups/{group_name:.*?}",
-                  action="show", conditions=dict(method=["GET"],
-                                                 function=check_group))
-        m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
+        m.connect("repos_group", "/repo_groups/{group_name:.*?}",
                   action="show", conditions=dict(method=["GET"],
                                                  function=check_group))
 
-    #ADMIN USER REST ROUTES
+        #EXTRAS REPO GROUP ROUTES
+        m.connect("edit_repo_group", "/repo_groups/{group_name:.*?}/edit",
+                  action="edit",
+                  conditions=dict(method=["GET"], function=check_group))
+        m.connect("edit_repo_group", "/repo_groups/{group_name:.*?}/edit",
+                  action="edit",
+                  conditions=dict(method=["PUT"], function=check_group))
+
+        m.connect("edit_repo_group_advanced", "/repo_groups/{group_name:.*?}/edit/advanced",
+                  action="edit_repo_group_advanced",
+                  conditions=dict(method=["GET"], function=check_group))
+        m.connect("edit_repo_group_advanced", "/repo_groups/{group_name:.*?}/edit/advanced",
+                  action="edit_repo_group_advanced",
+                  conditions=dict(method=["PUT"], function=check_group))
+
+        m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions",
+                  action="edit_repo_group_perms",
+                  conditions=dict(method=["GET"], function=check_group))
+        m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions",
+                  action="update_perms",
+                  conditions=dict(method=["PUT"], function=check_group))
+        m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions",
+                  action="delete_perms",
+                  conditions=dict(method=["DELETE"], function=check_group))
+
+        m.connect("delete_repos_group", "/repo_groups/{group_name:.*?}",
+                  action="delete", conditions=dict(method=["DELETE"],
+                                                   function=check_group_skip_path))
+
+
+    #ADMIN USER ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/users') as m:
         m.connect("users", "/users",
@@ -226,131 +187,209 @@
                   action="index", conditions=dict(method=["GET"]))
         m.connect("new_user", "/users/new",
                   action="new", conditions=dict(method=["GET"]))
-        m.connect("formatted_new_user", "/users/new.{format}",
-                  action="new", conditions=dict(method=["GET"]))
         m.connect("update_user", "/users/{id}",
                   action="update", conditions=dict(method=["PUT"]))
         m.connect("delete_user", "/users/{id}",
                   action="delete", conditions=dict(method=["DELETE"]))
         m.connect("edit_user", "/users/{id}/edit",
                   action="edit", conditions=dict(method=["GET"]))
-        m.connect("formatted_edit_user",
-                  "/users/{id}.{format}/edit",
-                  action="edit", conditions=dict(method=["GET"]))
         m.connect("user", "/users/{id}",
                   action="show", conditions=dict(method=["GET"]))
-        m.connect("formatted_user", "/users/{id}.{format}",
-                  action="show", conditions=dict(method=["GET"]))
 
         #EXTRAS USER ROUTES
-        m.connect("user_perm", "/users_perm/{id}",
-                  action="update_perm", conditions=dict(method=["PUT"]))
-        m.connect("user_emails", "/users_emails/{id}",
+        m.connect("edit_user_advanced", "/users/{id}/edit/advanced",
+                  action="edit_advanced", conditions=dict(method=["GET"]))
+        m.connect("edit_user_advanced", "/users/{id}/edit/advanced",
+                  action="update_advanced", conditions=dict(method=["PUT"]))
+
+        m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys",
+                  action="edit_api_keys", conditions=dict(method=["GET"]))
+        m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys",
+                  action="add_api_key", conditions=dict(method=["PUT"]))
+        m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys",
+                  action="delete_api_key", conditions=dict(method=["DELETE"]))
+
+        m.connect("edit_user_perms", "/users/{id}/edit/permissions",
+                  action="edit_perms", conditions=dict(method=["GET"]))
+        m.connect("edit_user_perms", "/users/{id}/edit/permissions",
+                  action="update_perms", conditions=dict(method=["PUT"]))
+
+        m.connect("edit_user_emails", "/users/{id}/edit/emails",
+                  action="edit_emails", conditions=dict(method=["GET"]))
+        m.connect("edit_user_emails", "/users/{id}/edit/emails",
                   action="add_email", conditions=dict(method=["PUT"]))
-        m.connect("user_emails_delete", "/users_emails/{id}",
+        m.connect("edit_user_emails", "/users/{id}/edit/emails",
                   action="delete_email", conditions=dict(method=["DELETE"]))
-        m.connect("user_ips", "/users_ips/{id}",
+
+        m.connect("edit_user_ips", "/users/{id}/edit/ips",
+                  action="edit_ips", conditions=dict(method=["GET"]))
+        m.connect("edit_user_ips", "/users/{id}/edit/ips",
                   action="add_ip", conditions=dict(method=["PUT"]))
-        m.connect("user_ips_delete", "/users_ips/{id}",
+        m.connect("edit_user_ips", "/users/{id}/edit/ips",
                   action="delete_ip", conditions=dict(method=["DELETE"]))
 
     #ADMIN USER GROUPS REST ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
-                        controller='admin/users_groups') as m:
-        m.connect("users_groups", "/users_groups",
+                        controller='admin/user_groups') as m:
+        m.connect("users_groups", "/user_groups",
                   action="create", conditions=dict(method=["POST"]))
-        m.connect("users_groups", "/users_groups",
-                  action="index", conditions=dict(method=["GET"]))
-        m.connect("formatted_users_groups", "/users_groups.{format}",
+        m.connect("users_groups", "/user_groups",
                   action="index", conditions=dict(method=["GET"]))
-        m.connect("new_users_group", "/users_groups/new",
-                  action="new", conditions=dict(method=["GET"]))
-        m.connect("formatted_new_users_group", "/users_groups/new.{format}",
+        m.connect("new_users_group", "/user_groups/new",
                   action="new", conditions=dict(method=["GET"]))
-        m.connect("update_users_group", "/users_groups/{id}",
+        m.connect("update_users_group", "/user_groups/{id}",
                   action="update", conditions=dict(method=["PUT"]))
-        m.connect("delete_users_group", "/users_groups/{id}",
+        m.connect("delete_users_group", "/user_groups/{id}",
                   action="delete", conditions=dict(method=["DELETE"]))
-        m.connect("edit_users_group", "/users_groups/{id}/edit",
+        m.connect("edit_users_group", "/user_groups/{id}/edit",
                   action="edit", conditions=dict(method=["GET"]),
                   function=check_user_group)
-        m.connect("formatted_edit_users_group",
-                  "/users_groups/{id}.{format}/edit",
-                  action="edit", conditions=dict(method=["GET"]))
-        m.connect("users_group", "/users_groups/{id}",
-                  action="show", conditions=dict(method=["GET"]))
-        m.connect("formatted_users_group", "/users_groups/{id}.{format}",
+        m.connect("users_group", "/user_groups/{id}",
                   action="show", conditions=dict(method=["GET"]))
 
-        #EXTRAS USER ROUTES
-        # update
-        m.connect("users_group_perm", "/users_groups/{id}/update_global_perm",
-                  action="update_perm", conditions=dict(method=["PUT"]))
+        #EXTRAS USER GROUP ROUTES
+        m.connect("edit_user_group_default_perms", "/user_groups/{id}/edit/default_perms",
+                  action="edit_default_perms", conditions=dict(method=["GET"]))
+        m.connect("edit_user_group_default_perms", "/user_groups/{id}/edit/default_perms",
+                  action="update_default_perms", conditions=dict(method=["PUT"]))
+
 
-        #add user group perm member
-        m.connect('set_user_group_perm_member', "/users_groups/{id}/grant_perm",
-             action="set_user_group_perm_member",
-             conditions=dict(method=["POST"]))
+        m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms",
+                  action="edit_perms", conditions=dict(method=["GET"]))
+        m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms",
+                  action="update_perms", conditions=dict(method=["PUT"]))
+        m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms",
+                  action="delete_perms", conditions=dict(method=["DELETE"]))
+
+        m.connect("edit_user_group_advanced", "/user_groups/{id}/edit/advanced",
+                  action="edit_advanced", conditions=dict(method=["GET"]))
+
+        m.connect("edit_user_group_members", "/user_groups/{id}/edit/members",
+                  action="edit_members", conditions=dict(method=["GET"]))
 
-        #ajax delete user group perm
-        m.connect('delete_user_group_perm_member', "/users_groups/{id}/revoke_perm",
-             action="delete_user_group_perm_member",
-             conditions=dict(method=["DELETE"]))
+
+
+    #ADMIN PERMISSIONS ROUTES
+    with rmap.submapper(path_prefix=ADMIN_PREFIX,
+                        controller='admin/permissions') as m:
+        m.connect("admin_permissions", "/permissions",
+                  action="permission_globals", conditions=dict(method=["POST"]))
+        m.connect("admin_permissions", "/permissions",
+                  action="permission_globals", conditions=dict(method=["GET"]))
 
-    #ADMIN GROUP REST ROUTES
-    rmap.resource('group', 'groups',
-                  controller='admin/groups', path_prefix=ADMIN_PREFIX)
+        m.connect("admin_permissions_ips", "/permissions/ips",
+                  action="permission_ips", conditions=dict(method=["POST"]))
+        m.connect("admin_permissions_ips", "/permissions/ips",
+                  action="permission_ips", conditions=dict(method=["GET"]))
 
-    #ADMIN PERMISSIONS REST ROUTES
-    rmap.resource('permission', 'permissions',
-                  controller='admin/permissions', path_prefix=ADMIN_PREFIX)
+        m.connect("admin_permissions_perms", "/permissions/perms",
+                  action="permission_perms", conditions=dict(method=["POST"]))
+        m.connect("admin_permissions_perms", "/permissions/perms",
+                  action="permission_perms", conditions=dict(method=["GET"]))
+
 
     #ADMIN DEFAULTS REST ROUTES
     rmap.resource('default', 'defaults',
                   controller='admin/defaults', path_prefix=ADMIN_PREFIX)
 
-    ##ADMIN LDAP SETTINGS
-    rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
-                 controller='admin/ldap_settings', action='ldap_settings',
+    #ADMIN AUTH SETTINGS
+    rmap.connect('auth_settings', '%s/auth' % ADMIN_PREFIX,
+                 controller='admin/auth_settings', action='auth_settings',
                  conditions=dict(method=["POST"]))
+    rmap.connect('auth_home', '%s/auth' % ADMIN_PREFIX,
+                 controller='admin/auth_settings')
 
-    rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
-                 controller='admin/ldap_settings')
-
-    #ADMIN SETTINGS REST ROUTES
+    #ADMIN SETTINGS ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/settings') as m:
         m.connect("admin_settings", "/settings",
-                  action="create", conditions=dict(method=["POST"]))
+                  action="settings_vcs", conditions=dict(method=["POST"]))
         m.connect("admin_settings", "/settings",
-                  action="index", conditions=dict(method=["GET"]))
-        m.connect("formatted_admin_settings", "/settings.{format}",
-                  action="index", conditions=dict(method=["GET"]))
-        m.connect("admin_new_setting", "/settings/new",
-                  action="new", conditions=dict(method=["GET"]))
-        m.connect("formatted_admin_new_setting", "/settings/new.{format}",
-                  action="new", conditions=dict(method=["GET"]))
-        m.connect("/settings/{setting_id}",
-                  action="update", conditions=dict(method=["PUT"]))
-        m.connect("/settings/{setting_id}",
-                  action="delete", conditions=dict(method=["DELETE"]))
-        m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
-                  action="edit", conditions=dict(method=["GET"]))
-        m.connect("formatted_admin_edit_setting",
-                  "/settings/{setting_id}.{format}/edit",
-                  action="edit", conditions=dict(method=["GET"]))
-        m.connect("admin_setting", "/settings/{setting_id}",
-                  action="show", conditions=dict(method=["GET"]))
-        m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
-                  action="show", conditions=dict(method=["GET"]))
-        m.connect("admin_settings_my_account", "/my_account",
+                  action="settings_vcs", conditions=dict(method=["GET"]))
+
+        m.connect("admin_settings_mapping", "/settings/mapping",
+                  action="settings_mapping", conditions=dict(method=["POST"]))
+        m.connect("admin_settings_mapping", "/settings/mapping",
+                  action="settings_mapping", conditions=dict(method=["GET"]))
+
+        m.connect("admin_settings_global", "/settings/global",
+                  action="settings_global", conditions=dict(method=["POST"]))
+        m.connect("admin_settings_global", "/settings/global",
+                  action="settings_global", conditions=dict(method=["GET"]))
+
+        m.connect("admin_settings_visual", "/settings/visual",
+                  action="settings_visual", conditions=dict(method=["POST"]))
+        m.connect("admin_settings_visual", "/settings/visual",
+                  action="settings_visual", conditions=dict(method=["GET"]))
+
+        m.connect("admin_settings_email", "/settings/email",
+                  action="settings_email", conditions=dict(method=["POST"]))
+        m.connect("admin_settings_email", "/settings/email",
+                  action="settings_email", conditions=dict(method=["GET"]))
+
+        m.connect("admin_settings_hooks", "/settings/hooks",
+                  action="settings_hooks", conditions=dict(method=["POST"]))
+        m.connect("admin_settings_hooks", "/settings/hooks",
+                  action="settings_hooks", conditions=dict(method=["DELETE"]))
+        m.connect("admin_settings_hooks", "/settings/hooks",
+                  action="settings_hooks", conditions=dict(method=["GET"]))
+
+        m.connect("admin_settings_search", "/settings/search",
+                  action="settings_search", conditions=dict(method=["POST"]))
+        m.connect("admin_settings_search", "/settings/search",
+                  action="settings_search", conditions=dict(method=["GET"]))
+
+        m.connect("admin_settings_system", "/settings/system",
+                  action="settings_system", conditions=dict(method=["POST"]))
+        m.connect("admin_settings_system", "/settings/system",
+                  action="settings_system", conditions=dict(method=["GET"]))
+        m.connect("admin_settings_system_update", "/settings/system/updates",
+                  action="settings_system_update", conditions=dict(method=["GET"]))
+
+        m.connect("admin_settings_license", "/settings/license",
+                  action="settings_license", conditions=dict(method=["POST"]))
+        m.connect("admin_settings_license", "/settings/license",
+                  action="settings_license", conditions=dict(method=["GET"]))
+
+    #ADMIN MY ACCOUNT
+    with rmap.submapper(path_prefix=ADMIN_PREFIX,
+                        controller='admin/my_account') as m:
+
+        m.connect("my_account", "/my_account",
                   action="my_account", conditions=dict(method=["GET"]))
-        m.connect("admin_settings_my_account_update", "/my_account_update",
-                  action="my_account_update", conditions=dict(method=["PUT"]))
-        m.connect("admin_settings_my_repos", "/my_account/repos",
-                  action="my_account_my_repos", conditions=dict(method=["GET"]))
-        m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
-                  action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
+        m.connect("my_account", "/my_account",
+                  action="my_account", conditions=dict(method=["POST"]))
+
+        m.connect("my_account_password", "/my_account/password",
+                  action="my_account_password", conditions=dict(method=["GET"]))
+        m.connect("my_account_password", "/my_account/password",
+                  action="my_account_password", conditions=dict(method=["POST"]))
+
+        m.connect("my_account_repos", "/my_account/repos",
+                  action="my_account_repos", conditions=dict(method=["GET"]))
+
+        m.connect("my_account_watched", "/my_account/watched",
+                  action="my_account_watched", conditions=dict(method=["GET"]))
+
+        m.connect("my_account_pullrequests", "/my_account/pull_requests",
+                  action="my_account_pullrequests", conditions=dict(method=["GET"]))
+
+        m.connect("my_account_perms", "/my_account/perms",
+                  action="my_account_perms", conditions=dict(method=["GET"]))
+
+        m.connect("my_account_emails", "/my_account/emails",
+                  action="my_account_emails", conditions=dict(method=["GET"]))
+        m.connect("my_account_emails", "/my_account/emails",
+                  action="my_account_emails_add", conditions=dict(method=["POST"]))
+        m.connect("my_account_emails", "/my_account/emails",
+                  action="my_account_emails_delete", conditions=dict(method=["DELETE"]))
+
+        m.connect("my_account_api_keys", "/my_account/api_keys",
+                  action="my_account_api_keys", conditions=dict(method=["GET"]))
+        m.connect("my_account_api_keys", "/my_account/api_keys",
+                  action="my_account_api_keys_add", conditions=dict(method=["POST"]))
+        m.connect("my_account_api_keys", "/my_account/api_keys",
+                  action="my_account_api_keys_delete", conditions=dict(method=["DELETE"]))
 
     #NOTIFICATION REST ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
@@ -390,24 +429,28 @@
                   action="index", conditions=dict(method=["GET"]))
         m.connect("new_gist", "/gists/new",
                   action="new", conditions=dict(method=["GET"]))
-        m.connect("formatted_new_gist", "/gists/new.{format}",
-                  action="new", conditions=dict(method=["GET"]))
-        m.connect("formatted_gists", "/gists.{format}",
-                  action="index", conditions=dict(method=["GET"]))
+
+
         m.connect("/gists/{gist_id}",
                   action="update", conditions=dict(method=["PUT"]))
         m.connect("/gists/{gist_id}",
                   action="delete", conditions=dict(method=["DELETE"]))
         m.connect("edit_gist", "/gists/{gist_id}/edit",
-                  action="edit", conditions=dict(method=["GET"]))
-        m.connect("formatted_edit_gist",
-                  "/gists/{gist_id}/{format}/edit",
-                  action="edit", conditions=dict(method=["GET"]))
+                  action="edit", conditions=dict(method=["GET", "POST"]))
+        m.connect("edit_gist_check_revision", "/gists/{gist_id}/edit/check_revision",
+                  action="check_revision", conditions=dict(method=["POST"]))
+
+
         m.connect("gist", "/gists/{gist_id}",
                   action="show", conditions=dict(method=["GET"]))
-        m.connect("formatted_gist", "/gists/{gist_id}/{format}",
+        m.connect("gist_rev", "/gists/{gist_id}/{revision}",
+                  revision="tip",
                   action="show", conditions=dict(method=["GET"]))
-        m.connect("formatted_gist_file", "/gists/{gist_id}/{format}/{revision}/{f_path:.*}",
+        m.connect("formatted_gist", "/gists/{gist_id}/{revision}/{format}",
+                  revision="tip",
+                  action="show", conditions=dict(method=["GET"]))
+        m.connect("formatted_gist_file", "/gists/{gist_id}/{revision}/{format}/{f_path:.*}",
+                  revision='tip',
                   action="show", conditions=dict(method=["GET"]))
 
     #ADMIN MAIN PAGES
@@ -489,32 +532,109 @@
     #==========================================================================
     # REPOSITORY ROUTES
     #==========================================================================
+    rmap.connect('repo_creating_home', '/{repo_name:.*?}/repo_creating',
+                controller='admin/repos', action='repo_creating')
+    rmap.connect('repo_check_home', '/{repo_name:.*?}/crepo_check',
+                controller='admin/repos', action='repo_check')
+
     rmap.connect('summary_home', '/{repo_name:.*?}',
                 controller='summary',
                 conditions=dict(function=check_repo))
 
+    # must be here for proper group/repo catching
+    rmap.connect('repos_group_home', '/{group_name:.*}',
+                controller='admin/repo_groups', action="show_by_name",
+                conditions=dict(function=check_group))
+    rmap.connect('repo_stats_home', '/{repo_name:.*?}/statistics',
+                controller='summary', action='statistics',
+                conditions=dict(function=check_repo))
+
     rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
                 controller='summary', action='repo_size',
                 conditions=dict(function=check_repo))
 
-    rmap.connect('repos_group_home', '/{group_name:.*}',
-                controller='admin/repos_groups', action="show_by_name",
-                conditions=dict(function=check_group))
+    rmap.connect('branch_tag_switcher', '/{repo_name:.*?}/branches-tags',
+                 controller='home', action='branch_tag_switcher')
+    rmap.connect('repo_refs_data', '/{repo_name:.*?}/refs-data',
+                 controller='home', action='repo_refs_data')
 
     rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
                 controller='changeset', revision='tip',
                 conditions=dict(function=check_repo))
+    rmap.connect('changeset_children', '/{repo_name:.*?}/changeset_children/{revision}',
+                controller='changeset', revision='tip', action="changeset_children",
+                conditions=dict(function=check_repo))
+    rmap.connect('changeset_parents', '/{repo_name:.*?}/changeset_parents/{revision}',
+                controller='changeset', revision='tip', action="changeset_parents",
+                conditions=dict(function=check_repo))
 
-    # no longer user, but kept for routes to work
-    rmap.connect("_edit_repo", "/{repo_name:.*?}/edit",
-                 controller='admin/repos', action="edit",
-                 conditions=dict(method=["GET"], function=check_repo)
-                 )
-
+    # repo edit options
     rmap.connect("edit_repo", "/{repo_name:.*?}/settings",
                  controller='admin/repos', action="edit",
-                 conditions=dict(method=["GET"], function=check_repo)
-                 )
+                 conditions=dict(method=["GET"], function=check_repo))
+
+    rmap.connect("edit_repo_perms", "/{repo_name:.*?}/settings/permissions",
+                 controller='admin/repos', action="edit_permissions",
+                 conditions=dict(method=["GET"], function=check_repo))
+    rmap.connect("edit_repo_perms_update", "/{repo_name:.*?}/settings/permissions",
+                 controller='admin/repos', action="edit_permissions_update",
+                 conditions=dict(method=["PUT"], function=check_repo))
+    rmap.connect("edit_repo_perms_revoke", "/{repo_name:.*?}/settings/permissions",
+                 controller='admin/repos', action="edit_permissions_revoke",
+                 conditions=dict(method=["DELETE"], function=check_repo))
+
+    rmap.connect("edit_repo_fields", "/{repo_name:.*?}/settings/fields",
+                 controller='admin/repos', action="edit_fields",
+                 conditions=dict(method=["GET"], function=check_repo))
+    rmap.connect('create_repo_fields', "/{repo_name:.*?}/settings/fields/new",
+                 controller='admin/repos', action="create_repo_field",
+                 conditions=dict(method=["PUT"], function=check_repo))
+    rmap.connect('delete_repo_fields', "/{repo_name:.*?}/settings/fields/{field_id}",
+                 controller='admin/repos', action="delete_repo_field",
+                 conditions=dict(method=["DELETE"], function=check_repo))
+
+
+    rmap.connect("edit_repo_advanced", "/{repo_name:.*?}/settings/advanced",
+                 controller='admin/repos', action="edit_advanced",
+                 conditions=dict(method=["GET"], function=check_repo))
+
+    rmap.connect("edit_repo_advanced_locking", "/{repo_name:.*?}/settings/advanced/locking",
+                 controller='admin/repos', action="edit_advanced_locking",
+                 conditions=dict(method=["PUT"], function=check_repo))
+    rmap.connect('toggle_locking', "/{repo_name:.*?}/settings/advanced/locking_toggle",
+                 controller='admin/repos', action="toggle_locking",
+                 conditions=dict(method=["GET"], function=check_repo))
+
+    rmap.connect("edit_repo_advanced_journal", "/{repo_name:.*?}/settings/advanced/journal",
+                 controller='admin/repos', action="edit_advanced_journal",
+                 conditions=dict(method=["PUT"], function=check_repo))
+
+    rmap.connect("edit_repo_advanced_fork", "/{repo_name:.*?}/settings/advanced/fork",
+                 controller='admin/repos', action="edit_advanced_fork",
+                 conditions=dict(method=["PUT"], function=check_repo))
+
+
+    rmap.connect("edit_repo_caches", "/{repo_name:.*?}/settings/caches",
+                 controller='admin/repos', action="edit_caches",
+                 conditions=dict(method=["GET"], function=check_repo))
+    rmap.connect("edit_repo_caches", "/{repo_name:.*?}/settings/caches",
+                 controller='admin/repos', action="edit_caches",
+                 conditions=dict(method=["PUT"], function=check_repo))
+
+
+    rmap.connect("edit_repo_remote", "/{repo_name:.*?}/settings/remote",
+                 controller='admin/repos', action="edit_remote",
+                 conditions=dict(method=["GET"], function=check_repo))
+    rmap.connect("edit_repo_remote", "/{repo_name:.*?}/settings/remote",
+                 controller='admin/repos', action="edit_remote",
+                 conditions=dict(method=["PUT"], function=check_repo))
+
+    rmap.connect("edit_repo_statistics", "/{repo_name:.*?}/settings/statistics",
+                 controller='admin/repos', action="edit_statistics",
+                 conditions=dict(method=["GET"], function=check_repo))
+    rmap.connect("edit_repo_statistics", "/{repo_name:.*?}/settings/statistics",
+                 controller='admin/repos', action="edit_statistics",
+                 conditions=dict(method=["PUT"], function=check_repo))
 
     #still working url for backward compat.
     rmap.connect('raw_changeset_home_depraced',
@@ -556,9 +676,14 @@
     rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
                  controller='changeset', action='changeset_info')
 
+    rmap.connect('compare_home',
+                 '/{repo_name:.*?}/compare',
+                 controller='compare', action='index',
+                 conditions=dict(function=check_repo))
+
     rmap.connect('compare_url',
                  '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
-                 controller='compare', action='index',
+                 controller='compare', action='compare',
                  conditions=dict(function=check_repo),
                  requirements=dict(
                             org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
@@ -648,6 +773,11 @@
                  controller='files', action='history', revision='tip', f_path='',
                  conditions=dict(function=check_repo))
 
+    rmap.connect('files_authors_home',
+                 '/{repo_name:.*?}/authors/{revision}/{f_path:.*}',
+                 controller='files', action='authors', revision='tip', f_path='',
+                 conditions=dict(function=check_repo))
+
     rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
                 controller='files', action='diff', revision='tip', f_path='',
                 conditions=dict(function=check_repo))
@@ -681,6 +811,11 @@
                  controller='files', action='add', revision='tip',
                  f_path='', conditions=dict(function=check_repo))
 
+    rmap.connect('files_delete_home',
+                 '/{repo_name:.*?}/delete/{revision}/{f_path:.*}',
+                 controller='files', action='delete', revision='tip',
+                 f_path='', conditions=dict(function=check_repo))
+
     rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
                 controller='files', action='archivefile',
                 conditions=dict(function=check_repo))
--- a/rhodecode/controllers/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
--- a/rhodecode/controllers/admin/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/admin/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
--- a/rhodecode/controllers/admin/admin.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/admin/admin.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.admin.admin
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Controller for Admin panel of Rhodecode
-
-    :created_on: Apr 7, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.admin
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Controller for Admin panel of Rhodecode
+
+:created_on: Apr 7, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import logging
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/controllers/admin/auth_settings.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.auth_settings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+pluggable authentication controller for RhodeCode
+
+:created_on: Nov 26, 2010
+:author: akesterson
+"""
+
+import pprint
+import logging
+import formencode.htmlfill
+import traceback
+
+from pylons import request, response, session, tmpl_context as c, url
+from pylons.controllers.util import abort, redirect
+from pylons.i18n.translation import _
+
+from sqlalchemy.exc import DatabaseError
+
+from rhodecode.lib import helpers as h
+from rhodecode.lib.compat import json, formatted_json
+from rhodecode.lib.base import BaseController, render
+from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
+from rhodecode.lib import auth_modules
+from rhodecode.model.forms import AuthSettingsForm
+from rhodecode.model.db import RhodeCodeSetting
+from rhodecode.model.meta import Session
+
+log = logging.getLogger(__name__)
+
+
+class AuthSettingsController(BaseController):
+
+    @LoginRequired()
+    @HasPermissionAllDecorator('hg.admin')
+    def __before__(self):
+        super(AuthSettingsController, self).__before__()
+
+    def __load_defaults(self):
+        c.available_plugins = [
+            'rhodecode.lib.auth_modules.auth_rhodecode',
+            'rhodecode.lib.auth_modules.auth_container',
+            'rhodecode.lib.auth_modules.auth_ldap',
+            'rhodecode.lib.auth_modules.auth_crowd',
+        ]
+        c.enabled_plugins = RhodeCodeSetting.get_auth_plugins()
+
+    def index(self, defaults=None, errors=None, prefix_error=False):
+        self.__load_defaults()
+        _defaults = {}
+        # default plugins loaded
+        formglobals = {
+            "auth_plugins": ["rhodecode.lib.auth_modules.auth_rhodecode"]
+        }
+        formglobals.update(RhodeCodeSetting.get_auth_settings())
+        formglobals["plugin_settings"] = {}
+        formglobals["auth_plugins_shortnames"] = {}
+        _defaults["auth_plugins"] = formglobals["auth_plugins"]
+
+        for module in formglobals["auth_plugins"]:
+            plugin = auth_modules.loadplugin(module)
+            plugin_name = plugin.name
+            formglobals["auth_plugins_shortnames"][module] = plugin_name
+            formglobals["plugin_settings"][module] = plugin.plugin_settings()
+            for v in formglobals["plugin_settings"][module]:
+                fullname = ("auth_" + plugin_name + "_" + v["name"])
+                if "default" in v:
+                    _defaults[fullname] = v["default"]
+                # Current values will be the default on the form, if there are any
+                setting = RhodeCodeSetting.get_by_name(fullname)
+                if setting:
+                    _defaults[fullname] = setting.app_settings_value
+        # we want to show , seperated list of enabled plugins
+        _defaults['auth_plugins'] = ','.join(_defaults['auth_plugins'])
+        if defaults:
+            _defaults.update(defaults)
+
+        formglobals["defaults"] = _defaults
+        # set template context variables
+        for k, v in formglobals.iteritems():
+            setattr(c, k, v)
+
+        log.debug(pprint.pformat(formglobals, indent=4))
+        log.debug(formatted_json(defaults))
+        return formencode.htmlfill.render(
+            render('admin/auth/auth_settings.html'),
+            defaults=_defaults,
+            errors=errors,
+            prefix_error=prefix_error,
+            encoding="UTF-8",
+            force_defaults=True,
+        )
+
+    def auth_settings(self):
+        """POST create and store auth settings"""
+        self.__load_defaults()
+        _form = AuthSettingsForm(c.enabled_plugins)()
+        log.debug("POST Result: %s" % formatted_json(dict(request.POST)))
+
+        try:
+            form_result = _form.to_python(dict(request.POST))
+            for k, v in form_result.items():
+                if k == 'auth_plugins':
+                    # we want to store it comma separated inside our settings
+                    v = ','.join(v)
+                log.debug("%s = %s" % (k, str(v)))
+                setting = RhodeCodeSetting.create_or_update(k, v)
+                Session().add(setting)
+            Session().commit()
+            h.flash(_('Auth settings updated successfully'),
+                       category='success')
+        except formencode.Invalid, errors:
+            log.error(traceback.format_exc())
+            e = errors.error_dict or {}
+            return self.index(
+                defaults=errors.value,
+                errors=e,
+                prefix_error=False)
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('error occurred during update of auth settings'),
+                    category='error')
+
+        return redirect(url('auth_home'))
--- a/rhodecode/controllers/admin/defaults.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/admin/defaults.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.admin.defaults
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    default settings controller for Rhodecode
-
-    :created_on: Apr 27, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.defaults
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+default settings controller for Rhodecode
+
+:created_on: Apr 27, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import logging
 import traceback
@@ -89,8 +89,7 @@
         try:
             form_result = _form.to_python(dict(request.POST))
             for k, v in form_result.iteritems():
-                setting = RhodeCodeSetting.get_by_name_or_create(k)
-                setting.app_settings_value = v
+                setting = RhodeCodeSetting.create_or_update(k, v)
                 Session().add(setting)
             Session().commit()
             h.flash(_('Default settings updated successfully'),
--- a/rhodecode/controllers/admin/gists.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/admin/gists.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.admin.gist
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    gist controller for RhodeCode
-
-    :created_on: May 9, 2013
-    :author: marcink
-    :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.gist
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+gist controller for RhodeCode
+
+:created_on: May 9, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import time
 import logging
 import traceback
@@ -35,15 +36,16 @@
 from rhodecode.model.forms import GistForm
 from rhodecode.model.gist import GistModel
 from rhodecode.model.meta import Session
-from rhodecode.model.db import Gist
+from rhodecode.model.db import Gist, User
 from rhodecode.lib import helpers as h
 from rhodecode.lib.base import BaseController, render
 from rhodecode.lib.auth import LoginRequired, NotAnonymous
+from rhodecode.lib.utils import jsonify
 from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime
 from rhodecode.lib.helpers import Page
 from webob.exc import HTTPNotFound, HTTPForbidden
 from sqlalchemy.sql.expression import or_
-from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
 
 log = logging.getLogger(__name__)
 
@@ -51,7 +53,7 @@
 class GistsController(BaseController):
     """REST Controller styled on the Atom Publishing Protocol"""
 
-    def __load_defaults(self):
+    def __load_defaults(self, extra_values=None):
         c.lifetime_values = [
             (str(-1), _('forever')),
             (str(5), _('5 minutes')),
@@ -59,27 +61,42 @@
             (str(60 * 24), _('1 day')),
             (str(60 * 24 * 30), _('1 month')),
         ]
+        if extra_values:
+            c.lifetime_values.append(extra_values)
         c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
 
     @LoginRequired()
-    def index(self, format='html'):
+    def index(self):
         """GET /admin/gists: All items in the collection"""
         # url('gists')
-        c.show_private = request.GET.get('private') and c.rhodecode_user.username != 'default'
-        c.show_public = request.GET.get('public') and c.rhodecode_user.username != 'default'
+        not_default_user = c.rhodecode_user.username != User.DEFAULT_USER
+        c.show_private = request.GET.get('private') and not_default_user
+        c.show_public = request.GET.get('public') and not_default_user
 
         gists = Gist().query()\
             .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
             .order_by(Gist.created_on.desc())
-        if c.show_private:
-            c.gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
+
+        # MY private
+        if c.show_private and not c.show_public:
+            gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
                              .filter(Gist.gist_owner == c.rhodecode_user.user_id)
-        elif c.show_public:
-            c.gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
+        # MY public
+        elif c.show_public and not c.show_private:
+            gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
                              .filter(Gist.gist_owner == c.rhodecode_user.user_id)
 
-        else:
-            c.gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
+        # MY public+private
+        elif c.show_private and c.show_public:
+            gists = gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
+                                     Gist.gist_type == Gist.GIST_PRIVATE))\
+                             .filter(Gist.gist_owner == c.rhodecode_user.user_id)
+
+        # default show ALL public gists
+        if not c.show_public and not c.show_private:
+            gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
+
+        c.gists = gists
         p = safe_int(request.GET.get('page', 1), 1)
         c.gists_pager = Page(c.gists, page=p, items_per_page=10)
         return render('admin/gists/index.html')
@@ -94,7 +111,7 @@
         try:
             form_result = gist_form.to_python(dict(request.POST))
             #TODO: multiple files support, from the form
-            filename = form_result['filename'] or 'gistfile1.txt'
+            filename = form_result['filename'] or Gist.DEFAULT_FILENAME
             nodes = {
                 filename: {
                     'content': form_result['content'],
@@ -105,7 +122,7 @@
             gist_type = Gist.GIST_PUBLIC if _public else Gist.GIST_PRIVATE
             gist = GistModel().create(
                 description=form_result['description'],
-                owner=c.rhodecode_user,
+                owner=c.rhodecode_user.user_id,
                 gist_mapping=nodes,
                 gist_type=gist_type,
                 lifetime=form_result['lifetime']
@@ -170,7 +187,7 @@
         return redirect(url('gists'))
 
     @LoginRequired()
-    def show(self, gist_id, format='html', revision='tip', f_path=None):
+    def show(self, gist_id, revision='tip', format='html', f_path=None):
         """GET /admin/gists/gist_id: Show a specific item"""
         # url('gist', gist_id=ID)
         c.gist = Gist.get_or_404(gist_id)
@@ -182,7 +199,8 @@
                           (time_to_datetime(c.gist.gist_expires)))
                 raise HTTPNotFound()
         try:
-            c.file_changeset, c.files = GistModel().get_gist_files(gist_id)
+            c.file_changeset, c.files = GistModel().get_gist_files(gist_id,
+                                                            revision=revision)
         except VCSError:
             log.error(traceback.format_exc())
             raise HTTPNotFound()
@@ -197,3 +215,78 @@
     def edit(self, gist_id, format='html'):
         """GET /admin/gists/gist_id/edit: Form to edit an existing item"""
         # url('edit_gist', gist_id=ID)
+        c.gist = Gist.get_or_404(gist_id)
+
+        #check if this gist is not expired
+        if c.gist.gist_expires != -1:
+            if time.time() > c.gist.gist_expires:
+                log.error('Gist expired at %s' %
+                          (time_to_datetime(c.gist.gist_expires)))
+                raise HTTPNotFound()
+        try:
+            c.file_changeset, c.files = GistModel().get_gist_files(gist_id)
+        except VCSError:
+            log.error(traceback.format_exc())
+            raise HTTPNotFound()
+
+        self.__load_defaults(extra_values=('0', _('unmodified')))
+        rendered = render('admin/gists/edit.html')
+
+        if request.POST:
+            rpost = request.POST
+            nodes = {}
+            for org_filename, filename, mimetype, content in zip(
+                                                    rpost.getall('org_files'),
+                                                    rpost.getall('files'),
+                                                    rpost.getall('mimetypes'),
+                                                    rpost.getall('contents')):
+
+                nodes[org_filename] = {
+                    'org_filename': org_filename,
+                    'filename': filename,
+                    'content': content,
+                    'lexer': mimetype,
+                }
+            try:
+                GistModel().update(
+                    gist=c.gist,
+                    description=rpost['description'],
+                    owner=c.gist.owner,
+                    gist_mapping=nodes,
+                    gist_type=c.gist.gist_type,
+                    lifetime=rpost['lifetime']
+                )
+
+                Session().commit()
+                h.flash(_('Successfully updated gist content'), category='success')
+            except NodeNotChangedError:
+                # raised if nothing was changed in repo itself. We anyway then
+                # store only DB stuff for gist
+                Session().commit()
+                h.flash(_('Successfully updated gist data'), category='success')
+            except Exception:
+                log.error(traceback.format_exc())
+                h.flash(_('Error occurred during update of gist %s') % gist_id,
+                        category='error')
+
+            return redirect(url('gist', gist_id=gist_id))
+
+        return rendered
+
+    @LoginRequired()
+    @NotAnonymous()
+    @jsonify
+    def check_revision(self, gist_id):
+        c.gist = Gist.get_or_404(gist_id)
+        last_rev = c.gist.scm_instance.get_changeset()
+        success = True
+        revision = request.POST.get('revision')
+
+        ##TODO: maybe move this to model ?
+        if revision != last_rev.raw_id:
+            log.error('Last revision %s is different then submited %s'
+                      % (revision, last_rev))
+            # our gist has newer version than we
+            success = False
+
+        return {'success': success}
--- a/rhodecode/controllers/admin/ldap_settings.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.admin.ldap_settings
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    ldap controller for RhodeCode
-
-    :created_on: Nov 26, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# 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 request, response, session, tmpl_context as c, url
-from pylons.controllers.util import abort, redirect
-from pylons.i18n.translation import _
-
-from sqlalchemy.exc import DatabaseError
-
-from rhodecode.lib.base import BaseController, render
-from rhodecode.lib import helpers as h
-from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
-from rhodecode.lib.exceptions import LdapImportError
-from rhodecode.model.forms import LdapSettingsForm
-from rhodecode.model.db import RhodeCodeSetting
-from rhodecode.model.meta import Session
-
-log = logging.getLogger(__name__)
-
-
-class LdapSettingsController(BaseController):
-
-    search_scope_choices = [('BASE', _('BASE'),),
-                            ('ONELEVEL', _('ONELEVEL'),),
-                            ('SUBTREE', _('SUBTREE'),),
-                            ]
-    search_scope_default = 'SUBTREE'
-
-    tls_reqcert_choices = [('NEVER', _('NEVER'),),
-                           ('ALLOW', _('ALLOW'),),
-                           ('TRY', _('TRY'),),
-                           ('DEMAND', _('DEMAND'),),
-                           ('HARD', _('HARD'),),
-                           ]
-    tls_reqcert_default = 'DEMAND'
-
-    tls_kind_choices = [('PLAIN', _('No encryption'),),
-                        ('LDAPS', _('LDAPS connection'),),
-                        ('START_TLS', _('START_TLS on LDAP connection'),)
-                        ]
-
-    tls_kind_default = 'PLAIN'
-
-    @LoginRequired()
-    @HasPermissionAllDecorator('hg.admin')
-    def __before__(self):
-        c.search_scope_choices = self.search_scope_choices
-        c.tls_reqcert_choices = self.tls_reqcert_choices
-        c.tls_kind_choices = self.tls_kind_choices
-
-        c.search_scope_cur = self.search_scope_default
-        c.tls_reqcert_cur = self.tls_reqcert_default
-        c.tls_kind_cur = self.tls_kind_default
-
-        super(LdapSettingsController, self).__before__()
-
-    def index(self):
-        defaults = RhodeCodeSetting.get_ldap_settings()
-        c.search_scope_cur = defaults.get('ldap_search_scope')
-        c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
-        c.tls_kind_cur = defaults.get('ldap_tls_kind')
-
-        return htmlfill.render(
-                    render('admin/ldap/ldap.html'),
-                    defaults=defaults,
-                    encoding="UTF-8",
-                    force_defaults=True,)
-
-    def ldap_settings(self):
-        """POST ldap create and store ldap settings"""
-
-        _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices],
-                                 [x[0] for x in self.search_scope_choices],
-                                 [x[0] for x in self.tls_kind_choices])()
-        # check the ldap lib
-        ldap_active = False
-        try:
-            import ldap
-            ldap_active = True
-        except ImportError:
-            pass
-
-        try:
-            form_result = _form.to_python(dict(request.POST))
-
-            try:
-
-                for k, v in form_result.items():
-                    if k.startswith('ldap_'):
-                        if k == 'ldap_active':
-                            v = v if ldap_active else False
-                        setting = RhodeCodeSetting.get_by_name(k)
-                        setting.app_settings_value = v
-                        Session().add(setting)
-
-                Session().commit()
-                h.flash(_('LDAP settings updated successfully'),
-                        category='success')
-                if not ldap_active:
-                    #if ldap is missing send an info to user
-                    h.flash(_('Unable to activate ldap. The "python-ldap" '
-                              'library is missing.'), category='warning')
-
-            except (DatabaseError,):
-                raise
-
-        except formencode.Invalid, errors:
-            e = errors.error_dict or {}
-
-            return htmlfill.render(
-                render('admin/ldap/ldap.html'),
-                defaults=errors.value,
-                errors=e,
-                prefix_error=False,
-                encoding="UTF-8")
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of ldap settings'),
-                    category='error')
-
-        return redirect(url('ldap_home'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/controllers/admin/my_account.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,290 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.my_account
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+my account controller for rhodecode admin
+
+:created_on: August 20, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import time
+import logging
+import traceback
+import formencode
+
+from sqlalchemy import func, or_
+from formencode import htmlfill
+from pylons import request, tmpl_context as c, url
+from pylons.controllers.util import redirect
+from pylons.i18n.translation import _
+
+from rhodecode.lib import helpers as h
+from rhodecode.lib.auth import LoginRequired, NotAnonymous, AuthUser
+from rhodecode.lib.base import BaseController, render
+from rhodecode.lib.utils2 import generate_api_key, safe_int
+from rhodecode.lib.compat import json
+from rhodecode.model.db import Repository, PullRequest, PullRequestReviewers, \
+    UserEmailMap, UserApiKeys, User, UserFollowing
+from rhodecode.model.forms import UserForm, PasswordChangeForm
+from rhodecode.model.user import UserModel
+from rhodecode.model.repo import RepoModel
+from rhodecode.model.api_key import ApiKeyModel
+from rhodecode.model.meta import Session
+
+log = logging.getLogger(__name__)
+
+
+class MyAccountController(BaseController):
+    """REST Controller styled on the Atom Publishing Protocol"""
+    # To properly map this controller, ensure your config/routing.py
+    # file has a resource setup:
+    #     map.resource('setting', 'settings', controller='admin/settings',
+    #         path_prefix='/admin', name_prefix='admin_')
+
+    @LoginRequired()
+    @NotAnonymous()
+    def __before__(self):
+        super(MyAccountController, self).__before__()
+
+    def __load_data(self):
+        c.user = User.get(self.rhodecode_user.user_id)
+        if c.user.username == User.DEFAULT_USER:
+            h.flash(_("You can't edit this user since it's"
+                      " crucial for entire application"), category='warning')
+            return redirect(url('users'))
+
+    def _load_my_repos_data(self, watched=False):
+        if watched:
+            admin = False
+            repos_list = [x.follows_repository for x in
+                          Session().query(UserFollowing).filter(
+                              UserFollowing.user_id ==
+                              self.rhodecode_user.user_id).all()]
+        else:
+            admin = True
+            repos_list = Session().query(Repository)\
+                         .filter(Repository.user_id ==
+                                 self.rhodecode_user.user_id)\
+                         .order_by(func.lower(Repository.repo_name)).all()
+
+        repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
+                                                   admin=admin)
+        #json used to render the grid
+        return json.dumps(repos_data)
+
+    def my_account(self):
+        """
+        GET /_admin/my_account Displays info about my account
+        """
+        # url('my_account')
+        c.active = 'profile'
+        self.__load_data()
+        c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id,
+                               ip_addr=self.ip_addr)
+        c.extern_type = c.user.extern_type
+        c.extern_name = c.user.extern_name
+
+        defaults = c.user.get_dict()
+        update = False
+        if request.POST:
+            _form = UserForm(edit=True,
+                             old_data={'user_id': self.rhodecode_user.user_id,
+                                       'email': self.rhodecode_user.email})()
+            form_result = {}
+            try:
+                post_data = dict(request.POST)
+                post_data['new_password'] = ''
+                post_data['password_confirmation'] = ''
+                form_result = _form.to_python(post_data)
+                # skip updating those attrs for my account
+                skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
+                              'new_password', 'password_confirmation']
+                #TODO: plugin should define if username can be updated
+                if c.extern_type != "rhodecode":
+                    # forbid updating username for external accounts
+                    skip_attrs.append('username')
+
+                UserModel().update(self.rhodecode_user.user_id, form_result,
+                                   skip_attrs=skip_attrs)
+                h.flash(_('Your account was updated successfully'),
+                        category='success')
+                Session().commit()
+                update = True
+
+            except formencode.Invalid, errors:
+                return htmlfill.render(
+                    render('admin/my_account/my_account.html'),
+                    defaults=errors.value,
+                    errors=errors.error_dict or {},
+                    prefix_error=False,
+                    encoding="UTF-8")
+            except Exception:
+                log.error(traceback.format_exc())
+                h.flash(_('Error occurred during update of user %s') \
+                        % form_result.get('username'), category='error')
+        if update:
+            return redirect('my_account')
+        return htmlfill.render(
+            render('admin/my_account/my_account.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False
+        )
+
+    def my_account_password(self):
+        c.active = 'password'
+        self.__load_data()
+        if request.POST:
+            _form = PasswordChangeForm(self.rhodecode_user.username)()
+            try:
+                form_result = _form.to_python(request.POST)
+                UserModel().update(self.rhodecode_user.user_id, form_result)
+                Session().commit()
+                h.flash(_("Successfully updated password"), category='success')
+            except formencode.Invalid as errors:
+                return htmlfill.render(
+                    render('admin/my_account/my_account.html'),
+                    defaults=errors.value,
+                    errors=errors.error_dict or {},
+                    prefix_error=False,
+                    encoding="UTF-8")
+            except Exception:
+                log.error(traceback.format_exc())
+                h.flash(_('Error occurred during update of user password'),
+                        category='error')
+        return render('admin/my_account/my_account.html')
+
+    def my_account_repos(self):
+        c.active = 'repos'
+        self.__load_data()
+
+        #json used to render the grid
+        c.data = self._load_my_repos_data()
+        return render('admin/my_account/my_account.html')
+
+    def my_account_watched(self):
+        c.active = 'watched'
+        self.__load_data()
+
+        #json used to render the grid
+        c.data = self._load_my_repos_data(watched=True)
+        return render('admin/my_account/my_account.html')
+
+    def my_account_perms(self):
+        c.active = 'perms'
+        self.__load_data()
+        c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id,
+                               ip_addr=self.ip_addr)
+
+        return render('admin/my_account/my_account.html')
+
+    def my_account_emails(self):
+        c.active = 'emails'
+        self.__load_data()
+
+        c.user_email_map = UserEmailMap.query()\
+            .filter(UserEmailMap.user == c.user).all()
+        return render('admin/my_account/my_account.html')
+
+    def my_account_emails_add(self):
+        email = request.POST.get('new_email')
+
+        try:
+            UserModel().add_extra_email(self.rhodecode_user.user_id, email)
+            Session().commit()
+            h.flash(_("Added email %s to user") % email, category='success')
+        except formencode.Invalid, error:
+            msg = error.error_dict['email']
+            h.flash(msg, category='error')
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('An error occurred during email saving'),
+                    category='error')
+        return redirect(url('my_account_emails'))
+
+    def my_account_emails_delete(self):
+        email_id = request.POST.get('del_email_id')
+        user_model = UserModel()
+        user_model.delete_extra_email(self.rhodecode_user.user_id, email_id)
+        Session().commit()
+        h.flash(_("Removed email from user"), category='success')
+        return redirect(url('my_account_emails'))
+
+    def my_account_pullrequests(self):
+        c.active = 'pullrequests'
+        self.__load_data()
+        c.show_closed = request.GET.get('pr_show_closed')
+
+        def _filter(pr):
+            s = sorted(pr, key=lambda o: o.created_on, reverse=True)
+            if not c.show_closed:
+                s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
+            return s
+
+        c.my_pull_requests = _filter(PullRequest.query()\
+                                .filter(PullRequest.user_id ==
+                                        self.rhodecode_user.user_id)\
+                                .all())
+        my_prs = [x.pull_request for x in PullRequestReviewers.query()
+                    .filter(PullRequestReviewers.user_id ==
+                        self.rhodecode_user.user_id).all()]
+        c.participate_in_pull_requests = _filter(my_prs)
+        return render('admin/my_account/my_account.html')
+
+    def my_account_api_keys(self):
+        c.active = 'api_keys'
+        self.__load_data()
+        show_expired = True
+        c.lifetime_values = [
+            (str(-1), _('forever')),
+            (str(5), _('5 minutes')),
+            (str(60), _('1 hour')),
+            (str(60 * 24), _('1 day')),
+            (str(60 * 24 * 30), _('1 month')),
+        ]
+        c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
+        c.user_api_keys = ApiKeyModel().get_api_keys(self.rhodecode_user.user_id,
+                                                     show_expired=show_expired)
+        return render('admin/my_account/my_account.html')
+
+    def my_account_api_keys_add(self):
+        lifetime = safe_int(request.POST.get('lifetime'), -1)
+        description = request.POST.get('description')
+        new_api_key = ApiKeyModel().create(self.rhodecode_user.user_id,
+                                           description, lifetime)
+        Session().commit()
+        h.flash(_("Api key successfully created"), category='success')
+        return redirect(url('my_account_api_keys'))
+
+    def my_account_api_keys_delete(self):
+        api_key = request.POST.get('del_api_key')
+        user_id = self.rhodecode_user.user_id
+        if request.POST.get('del_api_key_builtin'):
+            user = User.get(user_id)
+            if user:
+                user.api_key = generate_api_key(user.username)
+                Session().add(user)
+                Session().commit()
+                h.flash(_("Api key successfully reset"), category='success')
+        elif api_key:
+            ApiKeyModel().delete(api_key, self.rhodecode_user.user_id)
+            Session().commit()
+            h.flash(_("Api key successfully deleted"), category='success')
+
+        return redirect(url('my_account_api_keys'))
--- a/rhodecode/controllers/admin/notifications.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/admin/notifications.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.admin.notifications
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    notifications controller for RhodeCode
-
-    :created_on: Nov 23, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.notifications
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+notifications controller for RhodeCode
+
+:created_on: Nov 23, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import logging
 import traceback
@@ -29,6 +29,7 @@
 from pylons import request
 from pylons import tmpl_context as c, url
 from pylons.controllers.util import redirect, abort
+from webob.exc import HTTPBadRequest
 
 from rhodecode.model.db import Notification
 from rhodecode.model.notification import NotificationModel
@@ -111,13 +112,14 @@
             owner = all(un.user.user_id == c.rhodecode_user.user_id
                         for un in no.notifications_to_users)
             if h.HasPermissionAny('hg.admin')() or owner:
-                    NotificationModel().mark_read(c.rhodecode_user.user_id, no)
-                    Session().commit()
-                    return 'ok'
+                # deletes only notification2user
+                NotificationModel().mark_read(c.rhodecode_user.user_id, no)
+                Session().commit()
+                return 'ok'
         except Exception:
             Session().rollback()
             log.error(traceback.format_exc())
-        return 'fail'
+        raise HTTPBadRequest()
 
     def delete(self, notification_id):
         """DELETE /_admin/notifications/id: Delete an existing item"""
@@ -127,19 +129,19 @@
         #    h.form(url('notification', notification_id=ID),
         #           method='delete')
         # url('notification', notification_id=ID)
-
         try:
             no = Notification.get(notification_id)
-            owner = all(un.user.user_id == c.rhodecode_user.user_id
+            owner = any(un.user.user_id == c.rhodecode_user.user_id
                         for un in no.notifications_to_users)
             if h.HasPermissionAny('hg.admin')() or owner:
-                    NotificationModel().delete(c.rhodecode_user.user_id, no)
-                    Session().commit()
-                    return 'ok'
+                # deletes only notification2user
+                NotificationModel().delete(c.rhodecode_user.user_id, no)
+                Session().commit()
+                return 'ok'
         except Exception:
             Session().rollback()
             log.error(traceback.format_exc())
-        return 'fail'
+        raise HTTPBadRequest()
 
     def show(self, notification_id, format='html'):
         """GET /_admin/notifications/id: Show a specific item"""
@@ -149,8 +151,8 @@
 
         owner = any(un.user.user_id == c.rhodecode_user.user_id
                     for un in no.notifications_to_users)
-
-        if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
+        repo_admin = h.HasRepoPermissionAny('repository.admin')
+        if no and (h.HasPermissionAny('hg.admin')() or repo_admin or owner):
             unotification = NotificationModel()\
                             .get_user_notification(c.user.user_id, no)
 
--- a/rhodecode/controllers/admin/permissions.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/admin/permissions.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.admin.permissions
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    permissions controller for Rhodecode
-
-    :created_on: Apr 27, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.permissions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+permissions controller for Rhodecode
+
+:created_on: Apr 27, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import logging
 import traceback
@@ -55,6 +56,7 @@
     def __before__(self):
         super(PermissionsController, self).__before__()
 
+    def __load_data(self):
         c.repo_perms_choices = [('repository.none', _('None'),),
                                    ('repository.read', _('Read'),),
                                    ('repository.write', _('Write'),),
@@ -83,6 +85,11 @@
         c.repo_create_choices = [('hg.create.none', _('Disabled')),
                                  ('hg.create.repository', _('Enabled'))]
 
+        c.repo_create_on_write_choices = [
+            ('hg.create.write_on_repogroup.true', _('Enabled')),
+            ('hg.create.write_on_repogroup.false', _('Disabled')),
+        ]
+
         c.user_group_create_choices = [('hg.usergroup.create.false', _('Disabled')),
                                        ('hg.usergroup.create.true', _('Enabled'))]
 
@@ -92,50 +99,28 @@
         c.fork_choices = [('hg.fork.none', _('Disabled')),
                           ('hg.fork.repository', _('Enabled'))]
 
-    def index(self, format='html'):
-        """GET /permissions: All items in the collection"""
-        # url('permissions')
-
-    def create(self):
-        """POST /permissions: Create a new item"""
-        # url('permissions')
-
-    def new(self, format='html'):
-        """GET /permissions/new: Form to create a new item"""
-        # url('new_permission')
-
-    def update(self, id):
-        """PUT /permissions/id: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('permission', id=ID),
-        #           method='put')
-        # url('permission', id=ID)
-        if id == 'default':
-            c.user = default_user = User.get_default_user()
-            c.perm_user = AuthUser(user_id=default_user.user_id)
-            c.user_ip_map = UserIpMap.query()\
-                            .filter(UserIpMap.user == default_user).all()
-
+    def permission_globals(self):
+        c.active = 'globals'
+        self.__load_data()
+        if request.POST:
             _form = DefaultPermissionsForm(
-                    [x[0] for x in c.repo_perms_choices],
-                    [x[0] for x in c.group_perms_choices],
-                    [x[0] for x in c.user_group_perms_choices],
-                    [x[0] for x in c.repo_create_choices],
-                    [x[0] for x in c.repo_group_create_choices],
-                    [x[0] for x in c.user_group_create_choices],
-                    [x[0] for x in c.fork_choices],
-                    [x[0] for x in c.register_choices],
-                    [x[0] for x in c.extern_activate_choices],
-            )()
+                [x[0] for x in c.repo_perms_choices],
+                [x[0] for x in c.group_perms_choices],
+                [x[0] for x in c.user_group_perms_choices],
+                [x[0] for x in c.repo_create_choices],
+                [x[0] for x in c.repo_create_on_write_choices],
+                [x[0] for x in c.repo_group_create_choices],
+                [x[0] for x in c.user_group_create_choices],
+                [x[0] for x in c.fork_choices],
+                [x[0] for x in c.register_choices],
+                [x[0] for x in c.extern_activate_choices])()
 
             try:
                 form_result = _form.to_python(dict(request.POST))
-                form_result.update({'perm_user_name': id})
+                form_result.update({'perm_user_name': 'default'})
                 PermissionModel().update(form_result)
                 Session().commit()
-                h.flash(_('Default permissions updated successfully'),
+                h.flash(_('Global permissions updated successfully'),
                         category='success')
 
             except formencode.Invalid, errors:
@@ -152,66 +137,58 @@
                 h.flash(_('Error occurred during update of permissions'),
                         category='error')
 
-        return redirect(url('edit_permission', id=id))
+            return redirect(url('admin_permissions'))
+
+        c.user = User.get_default_user()
+        defaults = {'anonymous': c.user.active}
 
-    def delete(self, id):
-        """DELETE /permissions/id: Delete an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="DELETE" />
-        # Or using helpers:
-        #    h.form(url('permission', id=ID),
-        #           method='delete')
-        # url('permission', id=ID)
+        for p in c.user.user_perms:
+            if p.permission.permission_name.startswith('repository.'):
+                defaults['default_repo_perm'] = p.permission.permission_name
+
+            if p.permission.permission_name.startswith('group.'):
+                defaults['default_group_perm'] = p.permission.permission_name
 
-    def show(self, id, format='html'):
-        """GET /permissions/id: Show a specific item"""
-        # url('permission', id=ID)
-        Permission.get_or_404(-1)
+            if p.permission.permission_name.startswith('usergroup.'):
+                defaults['default_user_group_perm'] = p.permission.permission_name
 
-    def edit(self, id, format='html'):
-        """GET /permissions/id/edit: Form to edit an existing item"""
-        #url('edit_permission', id=ID)
+            if p.permission.permission_name.startswith('hg.create.write_on_repogroup'):
+                defaults['create_on_write'] = p.permission.permission_name
 
-        #this form can only edit default user permissions
-        if id == 'default':
-            c.user = User.get_default_user()
-            defaults = {'anonymous': c.user.active}
-            c.perm_user = c.user.AuthUser
-            c.user_ip_map = UserIpMap.query()\
-                            .filter(UserIpMap.user == c.user).all()
-            for p in c.user.user_perms:
-                if p.permission.permission_name.startswith('repository.'):
-                    defaults['default_repo_perm'] = p.permission.permission_name
+            elif p.permission.permission_name.startswith('hg.create.'):
+                defaults['default_repo_create'] = p.permission.permission_name
+
+            if p.permission.permission_name.startswith('hg.repogroup.'):
+                defaults['default_repo_group_create'] = p.permission.permission_name
+
+            if p.permission.permission_name.startswith('hg.usergroup.'):
+                defaults['default_user_group_create'] = p.permission.permission_name
 
-                if p.permission.permission_name.startswith('group.'):
-                    defaults['default_group_perm'] = p.permission.permission_name
-
-                if p.permission.permission_name.startswith('usergroup.'):
-                    defaults['default_user_group_perm'] = p.permission.permission_name
+            if p.permission.permission_name.startswith('hg.register.'):
+                defaults['default_register'] = p.permission.permission_name
 
-                if p.permission.permission_name.startswith('hg.create.'):
-                    defaults['default_repo_create'] = p.permission.permission_name
+            if p.permission.permission_name.startswith('hg.extern_activate.'):
+                defaults['default_extern_activate'] = p.permission.permission_name
 
-                if p.permission.permission_name.startswith('hg.repogroup.'):
-                    defaults['default_repo_group_create'] = p.permission.permission_name
-
-                if p.permission.permission_name.startswith('hg.usergroup.'):
-                    defaults['default_user_group_create'] = p.permission.permission_name
+            if p.permission.permission_name.startswith('hg.fork.'):
+                defaults['default_fork'] = p.permission.permission_name
 
-                if p.permission.permission_name.startswith('hg.register.'):
-                    defaults['default_register'] = p.permission.permission_name
-
-                if p.permission.permission_name.startswith('hg.extern_activate.'):
-                    defaults['default_extern_activate'] = p.permission.permission_name
-
-                if p.permission.permission_name.startswith('hg.fork.'):
-                    defaults['default_fork'] = p.permission.permission_name
+        return htmlfill.render(
+            render('admin/permissions/permissions.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
 
-            return htmlfill.render(
-                render('admin/permissions/permissions.html'),
-                defaults=defaults,
-                encoding="UTF-8",
-                force_defaults=False
-            )
-        else:
-            return redirect(url('admin_home'))
+    def permission_ips(self):
+        c.active = 'ips'
+        c.user = User.get_default_user()
+        c.user_ip_map = UserIpMap.query()\
+                        .filter(UserIpMap.user == c.user).all()
+
+        return render('admin/permissions/permissions.html')
+
+    def permission_perms(self):
+        c.active = 'perms'
+        c.user = User.get_default_user()
+        c.perm_user = c.user.AuthUser
+        return render('admin/permissions/permissions.html')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/controllers/admin/repo_groups.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,471 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.repo_groups
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Repository groups controller for RhodeCode
+
+:created_on: Mar 23, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import logging
+import traceback
+import formencode
+import itertools
+
+from formencode import htmlfill
+
+from pylons import request, tmpl_context as c, url
+from pylons.controllers.util import abort, redirect
+from pylons.i18n.translation import _, ungettext
+
+from sqlalchemy.exc import IntegrityError
+
+import rhodecode
+from rhodecode.lib import helpers as h
+from rhodecode.lib.compat import json
+from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
+    HasRepoGroupPermissionAnyDecorator, HasRepoGroupPermissionAll,\
+    HasPermissionAll
+from rhodecode.lib.base import BaseController, render
+from rhodecode.model.db import RepoGroup, Repository
+from rhodecode.model.scm import RepoGroupList
+from rhodecode.model.repo_group import RepoGroupModel
+from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
+from rhodecode.model.meta import Session
+from rhodecode.model.repo import RepoModel
+from webob.exc import HTTPInternalServerError, HTTPNotFound
+from rhodecode.lib.utils2 import str2bool, safe_int
+from sqlalchemy.sql.expression import func
+
+
+log = logging.getLogger(__name__)
+
+
+class RepoGroupsController(BaseController):
+    """REST Controller styled on the Atom Publishing Protocol"""
+
+    @LoginRequired()
+    def __before__(self):
+        super(RepoGroupsController, self).__before__()
+
+    def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
+        if HasPermissionAll('hg.admin')('group edit'):
+            #we're global admin, we're ok and we can create TOP level groups
+            allow_empty_group = True
+
+        #override the choices for this form, we need to filter choices
+        #and display only those we have ADMIN right
+        groups_with_admin_rights = RepoGroupList(RepoGroup.query().all(),
+                                                 perm_set=['group.admin'])
+        c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
+                                                 show_empty_group=allow_empty_group)
+        # exclude filtered ids
+        c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
+                               c.repo_groups)
+        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.user_groups_array = repo_model.get_user_groups_js()
+
+    def __load_data(self, group_id):
+        """
+        Load defaults settings for edit, and update
+
+        :param group_id:
+        """
+        repo_group = RepoGroup.get_or_404(group_id)
+        data = repo_group.get_dict()
+        data['group_name'] = repo_group.name
+
+        # fill repository group users
+        for p in repo_group.repo_group_to_perm:
+            data.update({'u_perm_%s' % p.user.username:
+                             p.permission.permission_name})
+
+        # fill repository group groups
+        for p in repo_group.users_group_to_perm:
+            data.update({'g_perm_%s' % p.users_group.users_group_name:
+                             p.permission.permission_name})
+
+        return data
+
+    def _revoke_perms_on_yourself(self, form_result):
+        _up = filter(lambda u: c.rhodecode_user.username == u[0],
+                     form_result['perms_updates'])
+        _new = filter(lambda u: c.rhodecode_user.username == u[0],
+                      form_result['perms_new'])
+        if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
+            return True
+        return False
+
+    def index(self, format='html'):
+        """GET /repo_groups: All items in the collection"""
+        # url('repos_groups')
+        _list = RepoGroup.query()\
+                    .order_by(func.lower(RepoGroup.group_name))\
+                    .all()
+        group_iter = RepoGroupList(_list, perm_set=['group.admin'])
+        repo_groups_data = []
+        total_records = len(group_iter)
+        _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
+        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
+
+        repo_group_name = lambda repo_group_name, children_groups: (
+            template.get_def("repo_group_name")
+            .render(repo_group_name, children_groups, _=_, h=h, c=c)
+        )
+        repo_group_actions = lambda repo_group_id, repo_group_name, gr_count: (
+            template.get_def("repo_group_actions")
+            .render(repo_group_id, repo_group_name, gr_count, _=_, h=h, c=c,
+                    ungettext=ungettext)
+        )
+
+        for repo_gr in group_iter:
+            children_groups = map(h.safe_unicode,
+                itertools.chain((g.name for g in repo_gr.parents),
+                                (x.name for x in [repo_gr])))
+            repo_count = repo_gr.repositories.count()
+            repo_groups_data.append({
+                "raw_name": repo_gr.group_name,
+                "group_name": repo_group_name(repo_gr.group_name, children_groups),
+                "desc": repo_gr.group_description,
+                "repos": repo_count,
+                "owner": h.person(repo_gr.user.username),
+                "action": repo_group_actions(repo_gr.group_id, repo_gr.group_name,
+                                             repo_count)
+            })
+
+        c.data = json.dumps({
+            "totalRecords": total_records,
+            "startIndex": 0,
+            "sort": None,
+            "dir": "asc",
+            "records": repo_groups_data
+        })
+
+        return render('admin/repo_groups/repo_groups.html')
+
+    def create(self):
+        """POST /repo_groups: Create a new item"""
+        # url('repos_groups')
+
+        self.__load_defaults()
+
+        # permissions for can create group based on parent_id are checked
+        # here in the Form
+        repo_group_form = RepoGroupForm(available_groups=
+                                map(lambda k: unicode(k[0]), c.repo_groups))()
+        try:
+            form_result = repo_group_form.to_python(dict(request.POST))
+            RepoGroupModel().create(
+                group_name=form_result['group_name'],
+                group_description=form_result['group_description'],
+                parent=form_result['group_parent_id'],
+                owner=self.rhodecode_user.user_id,
+                copy_permissions=form_result['group_copy_permissions']
+            )
+            Session().commit()
+            h.flash(_('Created repository group %s') \
+                    % form_result['group_name'], category='success')
+            #TODO: in futureaction_logger(, '', '', '', self.sa)
+        except formencode.Invalid, errors:
+            return htmlfill.render(
+                render('admin/repo_groups/repo_group_add.html'),
+                defaults=errors.value,
+                errors=errors.error_dict or {},
+                prefix_error=False,
+                encoding="UTF-8")
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('Error occurred during creation of repository group %s') \
+                    % request.POST.get('group_name'), category='error')
+        parent_group_id = form_result['group_parent_id']
+        #TODO: maybe we should get back to the main view, not the admin one
+        return redirect(url('repos_groups', parent_group=parent_group_id))
+
+    def new(self):
+        """GET /repo_groups/new: Form to create a new item"""
+        # url('new_repos_group')
+        if HasPermissionAll('hg.admin')('group create'):
+            #we're global admin, we're ok and we can create TOP level groups
+            pass
+        else:
+            # we pass in parent group into creation form, thus we know
+            # what would be the group, we can check perms here !
+            group_id = safe_int(request.GET.get('parent_group'))
+            group = RepoGroup.get(group_id) if group_id else None
+            group_name = group.group_name if group else None
+            if HasRepoGroupPermissionAll('group.admin')(group_name, 'group create'):
+                pass
+            else:
+                return abort(403)
+
+        self.__load_defaults()
+        return render('admin/repo_groups/repo_group_add.html')
+
+    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    def update(self, group_name):
+        """PUT /repo_groups/group_name: Update an existing item"""
+        # Forms posted to this method should contain a hidden field:
+        #    <input type="hidden" name="_method" value="PUT" />
+        # Or using helpers:
+        #    h.form(url('repos_group', group_name=GROUP_NAME),
+        #           method='put')
+        # url('repos_group', group_name=GROUP_NAME)
+
+        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        if HasPermissionAll('hg.admin')('group edit'):
+            #we're global admin, we're ok and we can create TOP level groups
+            allow_empty_group = True
+        elif not c.repo_group.parent_group:
+            allow_empty_group = True
+        else:
+            allow_empty_group = False
+        self.__load_defaults(allow_empty_group=allow_empty_group,
+                             exclude_group_ids=[c.repo_group.group_id])
+
+        repo_group_form = RepoGroupForm(
+            edit=True,
+            old_data=c.repo_group.get_dict(),
+            available_groups=c.repo_groups_choices,
+            can_create_in_root=allow_empty_group,
+        )()
+        try:
+            form_result = repo_group_form.to_python(dict(request.POST))
+
+            new_gr = RepoGroupModel().update(group_name, form_result)
+            Session().commit()
+            h.flash(_('Updated repository group %s') \
+                    % form_result['group_name'], category='success')
+            # we now have new name !
+            group_name = new_gr.group_name
+            #TODO: in future action_logger(, '', '', '', self.sa)
+        except formencode.Invalid, errors:
+
+            return htmlfill.render(
+                render('admin/repo_groups/repo_group_edit.html'),
+                defaults=errors.value,
+                errors=errors.error_dict or {},
+                prefix_error=False,
+                encoding="UTF-8")
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('Error occurred during update of repository group %s') \
+                    % request.POST.get('group_name'), category='error')
+
+        return redirect(url('edit_repo_group', group_name=group_name))
+
+    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    def delete(self, group_name):
+        """DELETE /repo_groups/group_name: Delete an existing item"""
+        # Forms posted to this method should contain a hidden field:
+        #    <input type="hidden" name="_method" value="DELETE" />
+        # Or using helpers:
+        #    h.form(url('repos_group', group_name=GROUP_NAME),
+        #           method='delete')
+        # url('repos_group', group_name=GROUP_NAME)
+
+        gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        repos = gr.repositories.all()
+        if repos:
+            h.flash(_('This group contains %s repositores and cannot be '
+                      'deleted') % len(repos), category='warning')
+            return redirect(url('repos_groups'))
+
+        children = gr.children.all()
+        if children:
+            h.flash(_('This group contains %s subgroups and cannot be deleted'
+                      % (len(children))), category='warning')
+            return redirect(url('repos_groups'))
+
+        try:
+            RepoGroupModel().delete(group_name)
+            Session().commit()
+            h.flash(_('Removed repository group %s') % group_name,
+                    category='success')
+            #TODO: in future action_logger(, '', '', '', self.sa)
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('Error occurred during deletion of repository group %s')
+                    % group_name, category='error')
+
+        return redirect(url('repos_groups'))
+
+    def show_by_name(self, group_name):
+        """
+        This is a proxy that does a lookup group_name -> id, and shows
+        the group by id view instead
+        """
+        group_name = group_name.rstrip('/')
+        id_ = RepoGroup.get_by_group_name(group_name)
+        if id_:
+            return self.show(group_name)
+        raise HTTPNotFound
+
+    @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
+                                         'group.admin')
+    def show(self, group_name):
+        """GET /repo_groups/group_name: Show a specific item"""
+        # url('repos_group', group_name=GROUP_NAME)
+        c.active = 'settings'
+
+        c.group = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        c.group_repos = c.group.repositories.all()
+
+        #overwrite our cached list with current filter
+        gr_filter = c.group_repos
+        c.repo_cnt = 0
+
+        groups = RepoGroup.query().order_by(RepoGroup.group_name)\
+            .filter(RepoGroup.group_parent_id == c.group.group_id).all()
+        c.groups = self.scm_model.get_repo_groups(groups)
+
+        c.repos_list = Repository.query()\
+                        .filter(Repository.group_id == c.group.group_id)\
+                        .order_by(func.lower(Repository.repo_name))\
+                        .all()
+
+        repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
+                                                   admin=False)
+        #json used to render the grid
+        c.data = json.dumps(repos_data)
+
+        return render('admin/repo_groups/repo_group_show.html')
+
+    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    def edit(self, group_name):
+        """GET /repo_groups/group_name/edit: Form to edit an existing item"""
+        # url('edit_repo_group', group_name=GROUP_NAME)
+        c.active = 'settings'
+
+        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        #we can only allow moving empty group if it's already a top-level
+        #group, ie has no parents, or we're admin
+        if HasPermissionAll('hg.admin')('group edit'):
+            #we're global admin, we're ok and we can create TOP level groups
+            allow_empty_group = True
+        elif not c.repo_group.parent_group:
+            allow_empty_group = True
+        else:
+            allow_empty_group = False
+
+        self.__load_defaults(allow_empty_group=allow_empty_group,
+                             exclude_group_ids=[c.repo_group.group_id])
+        defaults = self.__load_data(c.repo_group.group_id)
+
+        return htmlfill.render(
+            render('admin/repo_groups/repo_group_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False
+        )
+
+    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    def edit_repo_group_advanced(self, group_name):
+        """GET /repo_groups/group_name/edit: Form to edit an existing item"""
+        # url('edit_repo_group', group_name=GROUP_NAME)
+        c.active = 'advanced'
+        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+
+        return render('admin/repo_groups/repo_group_edit.html')
+
+    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    def edit_repo_group_perms(self, group_name):
+        """GET /repo_groups/group_name/edit: Form to edit an existing item"""
+        # url('edit_repo_group', group_name=GROUP_NAME)
+        c.active = 'perms'
+        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        self.__load_defaults()
+        defaults = self.__load_data(c.repo_group.group_id)
+
+        return htmlfill.render(
+            render('admin/repo_groups/repo_group_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False
+        )
+
+    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    def update_perms(self, group_name):
+        """
+        Update permissions for given repository group
+
+        :param group_name:
+        """
+
+        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        valid_recursive_choices = ['none', 'repos', 'groups', 'all']
+        form_result = RepoGroupPermsForm(valid_recursive_choices)().to_python(request.POST)
+        if not c.rhodecode_user.is_admin:
+            if self._revoke_perms_on_yourself(form_result):
+                msg = _('Cannot revoke permission for yourself as admin')
+                h.flash(msg, category='warning')
+                return redirect(url('edit_repo_group_perms', group_name=group_name))
+        recursive = form_result['recursive']
+        # iterate over all members(if in recursive mode) of this groups and
+        # set the permissions !
+        # this can be potentially heavy operation
+        RepoGroupModel()._update_permissions(c.repo_group,
+                                             form_result['perms_new'],
+                                             form_result['perms_updates'],
+                                             recursive)
+        #TODO: implement this
+        #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions',
+        #              repo_name, self.ip_addr, self.sa)
+        Session().commit()
+        h.flash(_('Repository Group permissions updated'), category='success')
+        return redirect(url('edit_repo_group_perms', group_name=group_name))
+
+    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    def delete_perms(self, group_name):
+        """
+        DELETE an existing repository group permission user
+
+        :param group_name:
+        """
+        try:
+            obj_type = request.POST.get('obj_type')
+            obj_id = None
+            if obj_type == 'user':
+                obj_id = safe_int(request.POST.get('user_id'))
+            elif obj_type == 'user_group':
+                obj_id = safe_int(request.POST.get('user_group_id'))
+
+            if not c.rhodecode_user.is_admin:
+                if obj_type == 'user' and c.rhodecode_user.user_id == obj_id:
+                    msg = _('Cannot revoke permission for yourself as admin')
+                    h.flash(msg, category='warning')
+                    raise Exception('revoke admin permission on self')
+            recursive = request.POST.get('recursive', 'none')
+            if obj_type == 'user':
+                RepoGroupModel().delete_permission(repo_group=group_name,
+                                                   obj=obj_id, obj_type='user',
+                                                   recursive=recursive)
+            elif obj_type == 'user_group':
+                RepoGroupModel().delete_permission(repo_group=group_name,
+                                                   obj=obj_id,
+                                                   obj_type='user_group',
+                                                   recursive=recursive)
+
+            Session().commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('An error occurred during revoking of permission'),
+                    category='error')
+            raise HTTPInternalServerError()
--- a/rhodecode/controllers/admin/repos.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/admin/repos.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.admin.repos
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Repositories controller for RhodeCode
-
-    :created_on: Apr 7, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,26 +11,36 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.repos
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Repositories controller for RhodeCode
+
+:created_on: Apr 7, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import logging
 import traceback
 import formencode
 from formencode import htmlfill
-
-from webob.exc import HTTPInternalServerError, HTTPForbidden
-from pylons import request, session, tmpl_context as c, url
+from webob.exc import HTTPInternalServerError, HTTPForbidden, HTTPNotFound
+from pylons import request, tmpl_context as c, url
 from pylons.controllers.util import redirect
 from pylons.i18n.translation import _
-from sqlalchemy.exc import IntegrityError
+from sqlalchemy.sql.expression import func
 
-import rhodecode
 from rhodecode.lib import helpers as h
 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
-    HasPermissionAnyDecorator, HasRepoPermissionAllDecorator, NotAnonymous,\
-    HasPermissionAny, HasReposGroupPermissionAny, HasRepoPermissionAnyDecorator
+    HasRepoPermissionAllDecorator, NotAnonymous,HasPermissionAny, \
+    HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
-from rhodecode.lib.utils import action_logger, repo_name_slug
+from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
 from rhodecode.lib.helpers import get_token
+from rhodecode.lib.vcs import RepositoryError
 from rhodecode.model.meta import Session
 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
     RhodeCodeSetting, RepositoryField
@@ -49,7 +48,6 @@
 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
 from rhodecode.model.repo import RepoModel
 from rhodecode.lib.compat import json
-from sqlalchemy.sql.expression import func
 from rhodecode.lib.exceptions import AttachedForksError
 from rhodecode.lib.utils2 import safe_int
 
@@ -67,15 +65,32 @@
     def __before__(self):
         super(ReposController, self).__before__()
 
-    def __load_defaults(self):
+    def _load_repo(self, repo_name):
+        repo_obj = Repository.get_by_repo_name(repo_name)
+
+        if repo_obj is None:
+            h.not_mapped_error(repo_name)
+            return redirect(url('repos'))
+
+        return repo_obj
+
+    def __load_defaults(self, repo=None):
         acl_groups = RepoGroupList(RepoGroup.query().all(),
                                perm_set=['group.write', 'group.admin'])
         c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
         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()
+        # in case someone no longer have a group.write access to a repository
+        # pre fill the list with this entry, we don't care if this is the same
+        # but it will allow saving repo data properly.
+
+        repo_group = None
+        if repo:
+            repo_group = repo.group
+        if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
+            c.repo_groups_choices.append(unicode(repo_group.group_id))
+            c.repo_groups.append(RepoGroup._generate_choice(repo_group))
+
         choices, c.landing_revs = ScmModel().get_repo_landing_revs()
         c.landing_revs_choices = choices
 
@@ -85,62 +100,24 @@
 
         :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.not_mapped_error(repo_name)
-            return redirect(url('repos'))
+        c.repo_info = self._load_repo(repo_name)
+        self.__load_defaults(c.repo_info)
 
         ##override defaults for exact repo info here git/hg etc
         choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
         c.landing_revs_choices = choices
-
-        c.default_user_id = User.get_default_user().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:
-            # this is on what revision we ended up so we add +1 for count
-            last_rev = c.repo_info.stats.stat_on_revision + 1
-        else:
-            last_rev = 0
-        c.stats_revision = last_rev
-
-        c.repo_last_rev = repo.count() 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)
-
-        c.repo_fields = RepositoryField.query()\
-            .filter(RepositoryField.repository == db_repo).all()
-
         defaults = RepoModel()._get_defaults(repo_name)
 
-        _repos = Repository.query().order_by(Repository.repo_name).all()
-        read_access_repos = RepoList(_repos)
-        c.repos_list = [('', _('--REMOVE FORK--'))]
-        c.repos_list += [(x.repo_id, x.repo_name)
-                         for x in read_access_repos
-                         if x.repo_id != c.repo_info.repo_id]
-
-        defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
         return defaults
 
     def index(self, format='html'):
         """GET /repos: All items in the collection"""
         # url('repos')
-        repo_list = Repository.query()\
-                                .order_by(func.lower(Repository.repo_name))\
-                                .all()
+        _list = Repository.query()\
+                        .order_by(func.lower(Repository.repo_name))\
+                        .all()
 
-        c.repos_list = RepoList(repo_list, perm_set=['repository.admin'])
+        c.repos_list = RepoList(_list, perm_set=['repository.admin'])
         repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
                                                    admin=True,
                                                    super_user_actions=True)
@@ -157,33 +134,19 @@
 
         self.__load_defaults()
         form_result = {}
+        task_id = None
         try:
+            # CanWriteToGroup validators checks permissions of this POST
             form_result = RepoForm(repo_groups=c.repo_groups_choices,
                                    landing_revs=c.landing_revs_choices)()\
                             .to_python(dict(request.POST))
 
-            new_repo = RepoModel().create(form_result,
-                                          self.rhodecode_user.user_id)
-            if form_result['clone_uri']:
-                h.flash(_('Created repository %s from %s') \
-                    % (form_result['repo_name'], form_result['clone_uri']),
-                    category='success')
-            else:
-                repo_url = h.link_to(form_result['repo_name'],
-                    h.url('summary_home', repo_name=form_result['repo_name_full']))
-                h.flash(h.literal(_('Created repository %s') % repo_url),
-                        category='success')
-
-            if request.POST.get('user_created'):
-                # created by regular non admin user
-                action_logger(self.rhodecode_user, 'user_created_repo',
-                              form_result['repo_name_full'], self.ip_addr,
-                              self.sa)
-            else:
-                action_logger(self.rhodecode_user, 'admin_created_repo',
-                              form_result['repo_name_full'], self.ip_addr,
-                              self.sa)
-            Session().commit()
+            # create is done sometimes async on celery, db transaction
+            # management is handled there.
+            task = RepoModel().create(form_result, self.rhodecode_user.user_id)
+            from celery.result import BaseAsyncResult
+            if isinstance(task, BaseAsyncResult):
+                task_id = task.task_id
         except formencode.Invalid, errors:
             return htmlfill.render(
                 render('admin/repos/repo_add.html'),
@@ -194,14 +157,14 @@
 
         except Exception:
             log.error(traceback.format_exc())
-            msg = _('Error creating repository %s') \
-                    % form_result.get('repo_name')
+            msg = (_('Error creating repository %s')
+                   % form_result.get('repo_name'))
             h.flash(msg, category='error')
-            if c.rhodecode_user.is_admin:
-                return redirect(url('repos'))
             return redirect(url('home'))
-        #redirect to our new repo !
-        return redirect(url('summary_home', repo_name=new_repo.repo_name))
+
+        return redirect(h.url('repo_creating_home',
+                              repo_name=form_result['repo_name_full'],
+                              task_id=task_id))
 
     @NotAnonymous()
     def create_repository(self):
@@ -213,7 +176,11 @@
             #but maybe you have at least write permission to a parent group ?
             _gr = RepoGroup.get(parent_group)
             gr_name = _gr.group_name if _gr else None
-            if not HasReposGroupPermissionAny('group.admin', 'group.write')(group_name=gr_name):
+            # create repositories with write permission on group is set to true
+            create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
+            group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
+            group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
+            if not (group_admin or (group_write and create_on_write)):
                 raise HTTPForbidden
 
         acl_groups = RepoGroupList(RepoGroup.query().all(),
@@ -237,6 +204,51 @@
             encoding="UTF-8"
         )
 
+    @LoginRequired()
+    @NotAnonymous()
+    def repo_creating(self, repo_name):
+        c.repo = repo_name
+        c.task_id = request.GET.get('task_id')
+        if not c.repo:
+            raise HTTPNotFound()
+        return render('admin/repos/repo_creating.html')
+
+    @LoginRequired()
+    @NotAnonymous()
+    @jsonify
+    def repo_check(self, repo_name):
+        c.repo = repo_name
+        task_id = request.GET.get('task_id')
+
+        if task_id and task_id not in ['None']:
+            from rhodecode import CELERY_ON
+            from celery.result import AsyncResult
+            if CELERY_ON:
+                task = AsyncResult(task_id)
+                if task.failed():
+                    raise HTTPInternalServerError(task.traceback)
+
+        repo = Repository.get_by_repo_name(repo_name)
+        if repo and repo.repo_state == Repository.STATE_CREATED:
+            if repo.clone_uri:
+                clone_uri = repo.clone_uri_hidden
+                h.flash(_('Created repository %s from %s')
+                        % (repo.repo_name, clone_uri), category='success')
+            else:
+                repo_url = h.link_to(repo.repo_name,
+                                     h.url('summary_home',
+                                           repo_name=repo.repo_name))
+                fork = repo.fork
+                if fork:
+                    fork_name = fork.repo_name
+                    h.flash(h.literal(_('Forked repository %s as %s')
+                            % (fork_name, repo_url)), category='success')
+                else:
+                    h.flash(h.literal(_('Created repository %s') % repo_url),
+                            category='success')
+            return {'result': True}
+        return {'result': False}
+
     @HasRepoPermissionAllDecorator('repository.admin')
     def update(self, repo_name):
         """
@@ -247,7 +259,12 @@
         #    h.form(url('repo', repo_name=ID),
         #           method='put')
         # url('repo', repo_name=ID)
-        self.__load_defaults()
+        c.repo_info = self._load_repo(repo_name)
+        c.active = 'settings'
+        c.repo_fields = RepositoryField.query()\
+            .filter(RepositoryField.repository == c.repo_info).all()
+        self.__load_defaults(c.repo_info)
+
         repo_model = RepoModel()
         changed_name = repo_name
         #override the choices with extracted revisions !
@@ -333,8 +350,46 @@
 
         return redirect(url('repos'))
 
+    @HasPermissionAllDecorator('hg.admin')
+    def show(self, repo_name, format='html'):
+        """GET /repos/repo_name: Show a specific item"""
+        # url('repo', repo_name=ID)
+
     @HasRepoPermissionAllDecorator('repository.admin')
-    def set_repo_perm_member(self, repo_name):
+    def edit(self, repo_name):
+        """GET /repo_name/settings: Form to edit an existing item"""
+        # url('edit_repo', repo_name=ID)
+        defaults = self.__load_data(repo_name)
+        if 'clone_uri' in defaults:
+            del defaults['clone_uri']
+
+        c.repo_fields = RepositoryField.query()\
+            .filter(RepositoryField.repository == c.repo_info).all()
+        c.active = 'settings'
+        return htmlfill.render(
+            render('admin/repos/repo_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
+
+    @HasRepoPermissionAllDecorator('repository.admin')
+    def edit_permissions(self, repo_name):
+        """GET /repo_name/settings: Form to edit an existing item"""
+        # url('edit_repo', repo_name=ID)
+        c.repo_info = self._load_repo(repo_name)
+        repo_model = RepoModel()
+        c.users_array = repo_model.get_users_js()
+        c.user_groups_array = repo_model.get_user_groups_js()
+        c.active = 'permissions'
+        defaults = RepoModel()._get_defaults(repo_name)
+
+        return htmlfill.render(
+            render('admin/repos/repo_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
+
+    def edit_permissions_update(self, repo_name):
         form = RepoPermsForm()().to_python(request.POST)
         RepoModel()._update_permissions(repo_name, form['perms_new'],
                                         form['perms_updates'])
@@ -343,15 +398,9 @@
         #              repo_name, self.ip_addr, self.sa)
         Session().commit()
         h.flash(_('Repository permissions updated'), category='success')
-        return redirect(url('edit_repo', repo_name=repo_name))
+        return redirect(url('edit_repo_perms', repo_name=repo_name))
 
-    @HasRepoPermissionAllDecorator('repository.admin')
-    def delete_repo_perm_member(self, repo_name):
-        """
-        DELETE an existing repository permission user
-
-        :param repo_name:
-        """
+    def edit_permissions_revoke(self, repo_name):
         try:
             obj_type = request.POST.get('obj_type')
             obj_id = None
@@ -363,7 +412,7 @@
             if obj_type == 'user':
                 RepoModel().revoke_user_permission(repo=repo_name, user=obj_id)
             elif obj_type == 'user_group':
-                RepoModel().revoke_users_group_permission(
+                RepoModel().revoke_user_group_permission(
                     repo=repo_name, group_name=obj_id
                 )
             #TODO: implement this
@@ -377,58 +426,155 @@
             raise HTTPInternalServerError()
 
     @HasRepoPermissionAllDecorator('repository.admin')
-    def repo_stats(self, repo_name):
+    def edit_fields(self, repo_name):
+        """GET /repo_name/settings: Form to edit an existing item"""
+        # url('edit_repo', repo_name=ID)
+        c.repo_info = self._load_repo(repo_name)
+        c.repo_fields = RepositoryField.query()\
+            .filter(RepositoryField.repository == c.repo_info).all()
+        c.active = 'fields'
+        if request.POST:
+
+            return redirect(url('repo_edit_fields'))
+        return render('admin/repos/repo_edit.html')
+
+    @HasRepoPermissionAllDecorator('repository.admin')
+    def create_repo_field(self, repo_name):
+        try:
+            form_result = RepoFieldForm()().to_python(dict(request.POST))
+            new_field = RepositoryField()
+            new_field.repository = Repository.get_by_repo_name(repo_name)
+            new_field.field_key = form_result['new_field_key']
+            new_field.field_type = form_result['new_field_type']  # python type
+            new_field.field_value = form_result['new_field_value']  # set initial blank value
+            new_field.field_desc = form_result['new_field_desc']
+            new_field.field_label = form_result['new_field_label']
+            Session().add(new_field)
+            Session().commit()
+        except Exception, e:
+            log.error(traceback.format_exc())
+            msg = _('An error occurred during creation of field')
+            if isinstance(e, formencode.Invalid):
+                msg += ". " + e.msg
+            h.flash(msg, category='error')
+        return redirect(url('edit_repo_fields', repo_name=repo_name))
+
+    @HasRepoPermissionAllDecorator('repository.admin')
+    def delete_repo_field(self, repo_name, field_id):
+        field = RepositoryField.get_or_404(field_id)
+        try:
+            Session().delete(field)
+            Session().commit()
+        except Exception, e:
+            log.error(traceback.format_exc())
+            msg = _('An error occurred during removal of field')
+            h.flash(msg, category='error')
+        return redirect(url('edit_repo_fields', repo_name=repo_name))
+
+    @HasRepoPermissionAllDecorator('repository.admin')
+    def edit_advanced(self, repo_name):
+        """GET /repo_name/settings: Form to edit an existing item"""
+        # url('edit_repo', repo_name=ID)
+        c.repo_info = self._load_repo(repo_name)
+        c.default_user_id = User.get_default_user().user_id
+        c.in_public_journal = UserFollowing.query()\
+            .filter(UserFollowing.user_id == c.default_user_id)\
+            .filter(UserFollowing.follows_repository == c.repo_info).scalar()
+
+        _repos = Repository.query().order_by(Repository.repo_name).all()
+        read_access_repos = RepoList(_repos)
+        c.repos_list = [(None, _('-- Not a fork --'))]
+        c.repos_list += [(x.repo_id, x.repo_name)
+                         for x in read_access_repos
+                         if x.repo_id != c.repo_info.repo_id]
+
+        defaults = {
+            'id_fork_of': c.repo_info.fork.repo_id if c.repo_info.fork else ''
+        }
+
+        c.active = 'advanced'
+        if request.POST:
+            return redirect(url('repo_edit_advanced'))
+        return htmlfill.render(
+            render('admin/repos/repo_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
+
+    @HasRepoPermissionAllDecorator('repository.admin')
+    def edit_advanced_journal(self, repo_name):
         """
-        DELETE an existing repository statistics
+        Set's this repository to be visible in public journal,
+        in other words assing default user to follow this repo
 
         :param repo_name:
         """
 
-        try:
-            RepoModel().delete_stats(repo_name)
-            Session().commit()
-        except Exception, e:
-            log.error(traceback.format_exc())
-            h.flash(_('An error occurred during deletion of repository stats'),
-                    category='error')
-        return redirect(url('edit_repo', repo_name=repo_name))
+        cur_token = request.POST.get('auth_token')
+        token = get_token()
+        if cur_token == token:
+            try:
+                repo_id = Repository.get_by_repo_name(repo_name).repo_id
+                user_id = User.get_default_user().user_id
+                self.scm_model.toggle_following_repo(repo_id, user_id)
+                h.flash(_('Updated repository visibility in public journal'),
+                        category='success')
+                Session().commit()
+            except Exception:
+                h.flash(_('An error occurred during setting this'
+                          ' repository in public journal'),
+                        category='error')
+
+        else:
+            h.flash(_('Token mismatch'), category='error')
+        return redirect(url('edit_repo_advanced', repo_name=repo_name))
+
 
     @HasRepoPermissionAllDecorator('repository.admin')
-    def repo_cache(self, repo_name):
+    def edit_advanced_fork(self, repo_name):
         """
-        INVALIDATE existing repository cache
+        Mark given repository as a fork of another
 
         :param repo_name:
         """
-
         try:
-            ScmModel().mark_for_invalidation(repo_name)
+            fork_id = request.POST.get('id_fork_of')
+            repo = ScmModel().mark_as_fork(repo_name, fork_id,
+                                           self.rhodecode_user.username)
+            fork = repo.fork.repo_name if repo.fork else _('Nothing')
             Session().commit()
+            h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
+                    category='success')
+        except RepositoryError, e:
+            log.error(traceback.format_exc())
+            h.flash(str(e), category='error')
         except Exception, e:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during cache invalidation'),
+            h.flash(_('An error occurred during this operation'),
                     category='error')
-        return redirect(url('edit_repo', repo_name=repo_name))
+
+        return redirect(url('edit_repo_advanced', repo_name=repo_name))
 
     @HasRepoPermissionAllDecorator('repository.admin')
-    def repo_locking(self, repo_name):
+    def edit_advanced_locking(self, repo_name):
         """
         Unlock repository when it is locked !
 
         :param repo_name:
         """
-
         try:
             repo = Repository.get_by_repo_name(repo_name)
             if request.POST.get('set_lock'):
                 Repository.lock(repo, c.rhodecode_user.user_id)
+                h.flash(_('Locked repository'), category='success')
             elif request.POST.get('set_unlock'):
                 Repository.unlock(repo)
+                h.flash(_('Unlocked repository'), category='success')
         except Exception, e:
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during unlocking'),
                     category='error')
-        return redirect(url('edit_repo', repo_name=repo_name))
+        return redirect(url('edit_repo_advanced', repo_name=repo_name))
 
     @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
     def toggle_locking(self, repo_name):
@@ -458,121 +604,72 @@
         return redirect(url('summary_home', repo_name=repo_name))
 
     @HasRepoPermissionAllDecorator('repository.admin')
-    def repo_public_journal(self, repo_name):
-        """
-        Set's this repository to be visible in public journal,
-        in other words assing default user to follow this repo
-
-        :param repo_name:
-        """
-
-        cur_token = request.POST.get('auth_token')
-        token = get_token()
-        if cur_token == token:
+    def edit_caches(self, repo_name):
+        """GET /repo_name/settings: Form to edit an existing item"""
+        # url('edit_repo', repo_name=ID)
+        c.repo_info = self._load_repo(repo_name)
+        c.active = 'caches'
+        if request.POST:
             try:
-                repo_id = Repository.get_by_repo_name(repo_name).repo_id
-                user_id = User.get_default_user().user_id
-                self.scm_model.toggle_following_repo(repo_id, user_id)
-                h.flash(_('Updated repository visibility in public journal'),
+                ScmModel().mark_for_invalidation(repo_name, delete=True)
+                Session().commit()
+                h.flash(_('Cache invalidation successful'),
                         category='success')
-                Session().commit()
-            except Exception:
-                h.flash(_('An error occurred during setting this'
-                          ' repository in public journal'),
+            except Exception, e:
+                log.error(traceback.format_exc())
+                h.flash(_('An error occurred during cache invalidation'),
                         category='error')
 
-        else:
-            h.flash(_('Token mismatch'), category='error')
-        return redirect(url('edit_repo', repo_name=repo_name))
-
-    @HasRepoPermissionAllDecorator('repository.admin')
-    def repo_pull(self, repo_name):
-        """
-        Runs task to update given repository with remote changes,
-        ie. make pull on remote location
-
-        :param repo_name:
-        """
-        try:
-            ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
-            h.flash(_('Pulled from remote location'), category='success')
-        except Exception, e:
-            log.error(traceback.format_exc())
-            h.flash(_('An error occurred during pull from remote location'),
-                    category='error')
-
-        return redirect(url('edit_repo', repo_name=repo_name))
+            return redirect(url('edit_repo_caches', repo_name=c.repo_name))
+        return render('admin/repos/repo_edit.html')
 
     @HasRepoPermissionAllDecorator('repository.admin')
-    def repo_as_fork(self, repo_name):
-        """
-        Mark given repository as a fork of another
-
-        :param repo_name:
-        """
-        try:
-            fork_id = request.POST.get('id_fork_of')
-            repo = ScmModel().mark_as_fork(repo_name, fork_id,
-                                    self.rhodecode_user.username)
-            fork = repo.fork.repo_name if repo.fork else _('Nothing')
-            Session().commit()
-            h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
-                    category='success')
-        except Exception, e:
-            log.error(traceback.format_exc())
-            h.flash(_('An error occurred during this operation'),
-                    category='error')
-
-        return redirect(url('edit_repo', repo_name=repo_name))
-
-    @HasPermissionAllDecorator('hg.admin')
-    def show(self, repo_name, format='html'):
-        """GET /repos/repo_name: Show a specific item"""
-        # url('repo', repo_name=ID)
+    def edit_remote(self, repo_name):
+        """GET /repo_name/settings: Form to edit an existing item"""
+        # url('edit_repo', repo_name=ID)
+        c.repo_info = self._load_repo(repo_name)
+        c.active = 'remote'
+        if request.POST:
+            try:
+                ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
+                h.flash(_('Pulled from remote location'), category='success')
+            except Exception, e:
+                log.error(traceback.format_exc())
+                h.flash(_('An error occurred during pull from remote location'),
+                        category='error')
+            return redirect(url('edit_repo_remote', repo_name=c.repo_name))
+        return render('admin/repos/repo_edit.html')
 
     @HasRepoPermissionAllDecorator('repository.admin')
-    def edit(self, repo_name, format='html'):
-        """GET /repos/repo_name/edit: Form to edit an existing item"""
+    def edit_statistics(self, repo_name):
+        """GET /repo_name/settings: Form to edit an existing item"""
         # url('edit_repo', repo_name=ID)
-        defaults = self.__load_data(repo_name)
+        c.repo_info = self._load_repo(repo_name)
+        repo = c.repo_info.scm_instance
 
-        return htmlfill.render(
-            render('admin/repos/repo_edit.html'),
-            defaults=defaults,
-            encoding="UTF-8",
-            force_defaults=False
-        )
+        if c.repo_info.stats:
+            # this is on what revision we ended up so we add +1 for count
+            last_rev = c.repo_info.stats.stat_on_revision + 1
+        else:
+            last_rev = 0
+        c.stats_revision = last_rev
+
+        c.repo_last_rev = repo.count() if repo.revisions else 0
 
-    @HasPermissionAllDecorator('hg.admin')
-    def create_repo_field(self, repo_name):
-        try:
-            form_result = RepoFieldForm()().to_python(dict(request.POST))
-            new_field = RepositoryField()
-            new_field.repository = Repository.get_by_repo_name(repo_name)
-            new_field.field_key = form_result['new_field_key']
-            new_field.field_type = form_result['new_field_type']  # python type
-            new_field.field_value = form_result['new_field_value']  # set initial blank value
-            new_field.field_desc = form_result['new_field_desc']
-            new_field.field_label = form_result['new_field_label']
-            Session().add(new_field)
-            Session().commit()
+        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)
 
-        except Exception, e:
-            log.error(traceback.format_exc())
-            msg = _('An error occurred during creation of field')
-            if isinstance(e, formencode.Invalid):
-                msg += ". " + e.msg
-            h.flash(msg, category='error')
-        return redirect(url('edit_repo', repo_name=repo_name))
+        c.active = 'statistics'
+        if request.POST:
+            try:
+                RepoModel().delete_stats(repo_name)
+                Session().commit()
+            except Exception, e:
+                log.error(traceback.format_exc())
+                h.flash(_('An error occurred during deletion of repository stats'),
+                        category='error')
+            return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
 
-    @HasPermissionAllDecorator('hg.admin')
-    def delete_repo_field(self, repo_name, field_id):
-        field = RepositoryField.get_or_404(field_id)
-        try:
-            Session().delete(field)
-            Session().commit()
-        except Exception, e:
-            log.error(traceback.format_exc())
-            msg = _('An error occurred during removal of field')
-            h.flash(msg, category='error')
-        return redirect(url('edit_repo', repo_name=repo_name))
+        return render('admin/repos/repo_edit.html')
--- a/rhodecode/controllers/admin/repos_groups.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,401 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.admin.repos_groups
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Repository groups controller for RhodeCode
-
-    :created_on: Mar 23, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# 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 traceback
-import formencode
-
-from formencode import htmlfill
-
-from pylons import request, tmpl_context as c, url
-from pylons.controllers.util import abort, redirect
-from pylons.i18n.translation import _
-
-from sqlalchemy.exc import IntegrityError
-
-import rhodecode
-from rhodecode.lib import helpers as h
-from rhodecode.lib.compat import json
-from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
-    HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
-    HasPermissionAll
-from rhodecode.lib.base import BaseController, render
-from rhodecode.model.db import RepoGroup, Repository
-from rhodecode.model.scm import RepoGroupList
-from rhodecode.model.repos_group import ReposGroupModel
-from rhodecode.model.forms import ReposGroupForm, RepoGroupPermsForm
-from rhodecode.model.meta import Session
-from rhodecode.model.repo import RepoModel
-from webob.exc import HTTPInternalServerError, HTTPNotFound
-from rhodecode.lib.utils2 import str2bool, safe_int
-from sqlalchemy.sql.expression import func
-
-
-log = logging.getLogger(__name__)
-
-
-class ReposGroupsController(BaseController):
-    """REST Controller styled on the Atom Publishing Protocol"""
-    # To properly map this controller, ensure your config/routing.py
-    # file has a resource setup:
-    #     map.resource('repos_group', 'repos_groups')
-
-    @LoginRequired()
-    def __before__(self):
-        super(ReposGroupsController, self).__before__()
-
-    def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
-        if HasPermissionAll('hg.admin')('group edit'):
-            #we're global admin, we're ok and we can create TOP level groups
-            allow_empty_group = True
-
-        #override the choices for this form, we need to filter choices
-        #and display only those we have ADMIN right
-        groups_with_admin_rights = RepoGroupList(RepoGroup.query().all(),
-                                             perm_set=['group.admin'])
-        c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
-                                                 show_empty_group=allow_empty_group)
-        # exclude filtered ids
-        c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
-                               c.repo_groups)
-        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()
-
-    def __load_data(self, group_id):
-        """
-        Load defaults settings for edit, and update
-
-        :param group_id:
-        """
-        repo_group = RepoGroup.get_or_404(group_id)
-        data = repo_group.get_dict()
-        data['group_name'] = repo_group.name
-
-        # fill repository group users
-        for p in repo_group.repo_group_to_perm:
-            data.update({'u_perm_%s' % p.user.username:
-                             p.permission.permission_name})
-
-        # fill repository group groups
-        for p in repo_group.users_group_to_perm:
-            data.update({'g_perm_%s' % p.users_group.users_group_name:
-                             p.permission.permission_name})
-
-        return data
-
-    def _revoke_perms_on_yourself(self, form_result):
-        _up = filter(lambda u: c.rhodecode_user.username == u[0],
-                     form_result['perms_updates'])
-        _new = filter(lambda u: c.rhodecode_user.username == u[0],
-                      form_result['perms_new'])
-        if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
-            return True
-        return False
-
-    def index(self, format='html'):
-        """GET /repos_groups: All items in the collection"""
-        # url('repos_groups')
-        group_iter = RepoGroupList(RepoGroup.query().all(),
-                                   perm_set=['group.admin'])
-        sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
-        c.groups = sorted(group_iter, key=sk)
-        return render('admin/repos_groups/repos_groups_show.html')
-
-    def create(self):
-        """POST /repos_groups: Create a new item"""
-        # url('repos_groups')
-
-        self.__load_defaults()
-
-        # permissions for can create group based on parent_id are checked
-        # here in the Form
-        repos_group_form = ReposGroupForm(available_groups=
-                                map(lambda k: unicode(k[0]), c.repo_groups))()
-        try:
-            form_result = repos_group_form.to_python(dict(request.POST))
-            ReposGroupModel().create(
-                    group_name=form_result['group_name'],
-                    group_description=form_result['group_description'],
-                    parent=form_result['group_parent_id'],
-                    owner=self.rhodecode_user.user_id
-            )
-            Session().commit()
-            h.flash(_('Created repository group %s') \
-                    % form_result['group_name'], category='success')
-            #TODO: in futureaction_logger(, '', '', '', self.sa)
-        except formencode.Invalid, errors:
-            return htmlfill.render(
-                render('admin/repos_groups/repos_groups_add.html'),
-                defaults=errors.value,
-                errors=errors.error_dict or {},
-                prefix_error=False,
-                encoding="UTF-8")
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('Error occurred during creation of repository group %s') \
-                    % request.POST.get('group_name'), category='error')
-        parent_group_id = form_result['group_parent_id']
-        #TODO: maybe we should get back to the main view, not the admin one
-        return redirect(url('repos_groups', parent_group=parent_group_id))
-
-    def new(self, format='html'):
-        """GET /repos_groups/new: Form to create a new item"""
-        # url('new_repos_group')
-        if HasPermissionAll('hg.admin')('group create'):
-            #we're global admin, we're ok and we can create TOP level groups
-            pass
-        else:
-            # we pass in parent group into creation form, thus we know
-            # what would be the group, we can check perms here !
-            group_id = safe_int(request.GET.get('parent_group'))
-            group = RepoGroup.get(group_id) if group_id else None
-            group_name = group.group_name if group else None
-            if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
-                pass
-            else:
-                return abort(403)
-
-        self.__load_defaults()
-        return render('admin/repos_groups/repos_groups_add.html')
-
-    @HasReposGroupPermissionAnyDecorator('group.admin')
-    def update(self, group_name):
-        """PUT /repos_groups/group_name: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('repos_group', group_name=GROUP_NAME),
-        #           method='put')
-        # url('repos_group', group_name=GROUP_NAME)
-
-        c.repos_group = ReposGroupModel()._get_repo_group(group_name)
-        if HasPermissionAll('hg.admin')('group edit'):
-            #we're global admin, we're ok and we can create TOP level groups
-            allow_empty_group = True
-        elif not c.repos_group.parent_group:
-            allow_empty_group = True
-        else:
-            allow_empty_group = False
-        self.__load_defaults(allow_empty_group=allow_empty_group,
-                             exclude_group_ids=[c.repos_group.group_id])
-
-        repos_group_form = ReposGroupForm(
-            edit=True,
-            old_data=c.repos_group.get_dict(),
-            available_groups=c.repo_groups_choices,
-            can_create_in_root=allow_empty_group,
-        )()
-        try:
-            form_result = repos_group_form.to_python(dict(request.POST))
-
-            new_gr = ReposGroupModel().update(group_name, form_result)
-            Session().commit()
-            h.flash(_('Updated repository group %s') \
-                    % form_result['group_name'], category='success')
-            # we now have new name !
-            group_name = new_gr.group_name
-            #TODO: in future action_logger(, '', '', '', self.sa)
-        except formencode.Invalid, errors:
-
-            return htmlfill.render(
-                render('admin/repos_groups/repos_groups_edit.html'),
-                defaults=errors.value,
-                errors=errors.error_dict or {},
-                prefix_error=False,
-                encoding="UTF-8")
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of repository group %s') \
-                    % request.POST.get('group_name'), category='error')
-
-        return redirect(url('edit_repos_group', group_name=group_name))
-
-    @HasReposGroupPermissionAnyDecorator('group.admin')
-    def delete(self, group_name):
-        """DELETE /repos_groups/group_name: Delete an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="DELETE" />
-        # Or using helpers:
-        #    h.form(url('repos_group', group_name=GROUP_NAME),
-        #           method='delete')
-        # url('repos_group', group_name=GROUP_NAME)
-
-        gr = c.repos_group = ReposGroupModel()._get_repo_group(group_name)
-        repos = gr.repositories.all()
-        if repos:
-            h.flash(_('This group contains %s repositores and cannot be '
-                      'deleted') % len(repos), category='warning')
-            return redirect(url('repos_groups'))
-
-        children = gr.children.all()
-        if children:
-            h.flash(_('This group contains %s subgroups and cannot be deleted'
-                      % (len(children))), category='warning')
-            return redirect(url('repos_groups'))
-
-        try:
-            ReposGroupModel().delete(group_name)
-            Session().commit()
-            h.flash(_('Removed repository group %s') % group_name,
-                    category='success')
-            #TODO: in future action_logger(, '', '', '', self.sa)
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('Error occurred during deletion of repository group %s')
-                    % group_name, category='error')
-
-        return redirect(url('repos_groups'))
-
-    @HasReposGroupPermissionAnyDecorator('group.admin')
-    def set_repo_group_perm_member(self, group_name):
-        c.repos_group = ReposGroupModel()._get_repo_group(group_name)
-        form_result = RepoGroupPermsForm()().to_python(request.POST)
-        if not c.rhodecode_user.is_admin:
-            if self._revoke_perms_on_yourself(form_result):
-                msg = _('Cannot revoke permission for yourself as admin')
-                h.flash(msg, category='warning')
-                return redirect(url('edit_repos_group', group_name=group_name))
-        recursive = form_result['recursive']
-        # iterate over all members(if in recursive mode) of this groups and
-        # set the permissions !
-        # this can be potentially heavy operation
-        ReposGroupModel()._update_permissions(c.repos_group,
-                                              form_result['perms_new'],
-                                              form_result['perms_updates'],
-                                              recursive)
-        #TODO: implement this
-        #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions',
-        #              repo_name, self.ip_addr, self.sa)
-        Session().commit()
-        h.flash(_('Repository Group permissions updated'), category='success')
-        return redirect(url('edit_repos_group', group_name=group_name))
-
-    @HasReposGroupPermissionAnyDecorator('group.admin')
-    def delete_repo_group_perm_member(self, group_name):
-        """
-        DELETE an existing repository group permission user
-
-        :param group_name:
-        """
-        try:
-            obj_type = request.POST.get('obj_type')
-            obj_id = None
-            if obj_type == 'user':
-                obj_id = safe_int(request.POST.get('user_id'))
-            elif obj_type == 'user_group':
-                obj_id = safe_int(request.POST.get('user_group_id'))
-
-            if not c.rhodecode_user.is_admin:
-                if obj_type == 'user' and c.rhodecode_user.user_id == obj_id:
-                    msg = _('Cannot revoke permission for yourself as admin')
-                    h.flash(msg, category='warning')
-                    raise Exception('revoke admin permission on self')
-            recursive = str2bool(request.POST.get('recursive', False))
-            if obj_type == 'user':
-                ReposGroupModel().delete_permission(
-                    repos_group=group_name, obj=obj_id,
-                    obj_type='user', recursive=recursive
-                )
-            elif obj_type == 'user_group':
-                ReposGroupModel().delete_permission(
-                    repos_group=group_name, obj=obj_id,
-                    obj_type='users_group', recursive=recursive
-                )
-
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('An error occurred during revoking of permission'),
-                    category='error')
-            raise HTTPInternalServerError()
-
-    def show_by_name(self, group_name):
-        """
-        This is a proxy that does a lookup group_name -> id, and shows
-        the group by id view instead
-        """
-        group_name = group_name.rstrip('/')
-        id_ = RepoGroup.get_by_group_name(group_name)
-        if id_:
-            return self.show(id_.group_id)
-        raise HTTPNotFound
-
-    @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
-                                         'group.admin')
-    def show(self, group_name, format='html'):
-        """GET /repos_groups/group_name: Show a specific item"""
-        # url('repos_group', group_name=GROUP_NAME)
-
-        c.group = c.repos_group = ReposGroupModel()._get_repo_group(group_name)
-        c.group_repos = c.group.repositories.all()
-
-        #overwrite our cached list with current filter
-        gr_filter = c.group_repos
-        c.repo_cnt = 0
-
-        groups = RepoGroup.query().order_by(RepoGroup.group_name)\
-            .filter(RepoGroup.group_parent_id == c.group.group_id).all()
-        c.groups = self.scm_model.get_repos_groups(groups)
-
-        c.repos_list = Repository.query()\
-                        .filter(Repository.group_id == c.group.group_id)\
-                        .order_by(func.lower(Repository.repo_name))\
-                        .all()
-
-        repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
-                                                   admin=False)
-        #json used to render the grid
-        c.data = json.dumps(repos_data)
-
-        return render('admin/repos_groups/repos_groups.html')
-
-    @HasReposGroupPermissionAnyDecorator('group.admin')
-    def edit(self, group_name, format='html'):
-        """GET /repos_groups/group_name/edit: Form to edit an existing item"""
-        # url('edit_repos_group', group_name=GROUP_NAME)
-
-        c.repos_group = ReposGroupModel()._get_repo_group(group_name)
-        #we can only allow moving empty group if it's already a top-level
-        #group, ie has no parents, or we're admin
-        if HasPermissionAll('hg.admin')('group edit'):
-            #we're global admin, we're ok and we can create TOP level groups
-            allow_empty_group = True
-        elif not c.repos_group.parent_group:
-            allow_empty_group = True
-        else:
-            allow_empty_group = False
-
-        self.__load_defaults(allow_empty_group=allow_empty_group,
-                             exclude_group_ids=[c.repos_group.group_id])
-        defaults = self.__load_data(c.repos_group.group_id)
-
-        return htmlfill.render(
-            render('admin/repos_groups/repos_groups_edit.html'),
-            defaults=defaults,
-            encoding="UTF-8",
-            force_defaults=False
-        )
--- a/rhodecode/controllers/admin/settings.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/admin/settings.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.admin.settings
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    settings controller for rhodecode admin
-
-    :created_on: Jul 14, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,39 +11,42 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.settings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+settings controller for rhodecode admin
+
+:created_on: Jul 14, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import time
 import logging
 import traceback
 import formencode
-import pkg_resources
-import platform
 
-from sqlalchemy import func
 from formencode import htmlfill
-from pylons import request, session, tmpl_context as c, url, config
-from pylons.controllers.util import abort, redirect
+from pylons import request, tmpl_context as c, url, config
+from pylons.controllers.util import redirect
 from pylons.i18n.translation import _
 
 from rhodecode.lib import helpers as h
-from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
-    HasPermissionAnyDecorator, NotAnonymous, HasPermissionAny,\
-    HasReposGroupPermissionAll, HasReposGroupPermissionAny, AuthUser
+from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
 from rhodecode.lib.base import BaseController, render
 from rhodecode.lib.celerylib import tasks, run_task
 from rhodecode.lib.exceptions import HgsubversionImportError
-from rhodecode.lib.utils import repo2db_mapper, set_rhodecode_config, \
-    check_git_version
-from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
-    RhodeCodeSetting, PullRequest, PullRequestReviewers
-from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
+from rhodecode.lib.utils import repo2db_mapper, set_rhodecode_config
+from rhodecode.model.db import RhodeCodeUi, Repository, RhodeCodeSetting
+from rhodecode.model.forms import ApplicationSettingsForm, \
     ApplicationUiSettingsForm, ApplicationVisualisationForm
-from rhodecode.model.scm import ScmModel, RepoGroupList
-from rhodecode.model.user import UserModel
-from rhodecode.model.repo import RepoModel
-from rhodecode.model.db import User
+from rhodecode.model.license import LicenseModel
+from rhodecode.model.scm import ScmModel
 from rhodecode.model.notification import EmailNotificationModel
 from rhodecode.model.meta import Session
-from rhodecode.lib.utils2 import str2bool, safe_unicode
+from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str
 from rhodecode.lib.compat import json
 log = logging.getLogger(__name__)
 
@@ -69,168 +61,37 @@
     @LoginRequired()
     def __before__(self):
         super(SettingsController, self).__before__()
-        c.modules = sorted([(p.project_name, p.version)
-                            for p in pkg_resources.working_set]
-                           + [('git', check_git_version())],
-                           key=lambda k: k[0].lower())
-        c.py_version = platform.python_version()
-        c.platform = platform.platform()
 
-    @HasPermissionAllDecorator('hg.admin')
-    def index(self, format='html'):
-        """GET /admin/settings: All items in the collection"""
-        # url('admin_settings')
-
-        defaults = RhodeCodeSetting.get_app_settings()
-        defaults.update(self._get_hg_ui_settings())
+    def _get_hg_ui_settings(self):
+        ret = RhodeCodeUi.query().all()
 
-        return htmlfill.render(
-            render('admin/settings/settings.html'),
-            defaults=defaults,
-            encoding="UTF-8",
-            force_defaults=False
-        )
+        if not ret:
+            raise Exception('Could not get application ui settings !')
+        settings = {}
+        for each in ret:
+            k = each.ui_key
+            v = each.ui_value
+            if k == '/':
+                k = 'root_path'
 
-    @HasPermissionAllDecorator('hg.admin')
-    def create(self):
-        """POST /admin/settings: Create a new item"""
-        # url('admin_settings')
+            if k == 'push_ssl':
+                v = str2bool(v)
+
+            if k.find('.') != -1:
+                k = k.replace('.', '_')
 
-    @HasPermissionAllDecorator('hg.admin')
-    def new(self, format='html'):
-        """GET /admin/settings/new: Form to create a new item"""
-        # url('admin_new_setting')
+            if each.ui_section in ['hooks', 'extensions']:
+                v = each.ui_active
+
+            settings[each.ui_section + '_' + k] = v
+        return settings
 
     @HasPermissionAllDecorator('hg.admin')
-    def update(self, setting_id):
-        """PUT /admin/settings/setting_id: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('admin_setting', setting_id=ID),
-        #           method='put')
-        # url('admin_setting', setting_id=ID)
-
-        if setting_id == 'mapping':
-            rm_obsolete = request.POST.get('destroy', False)
-            invalidate_cache = request.POST.get('invalidate', False)
-            log.debug('rescanning repo location with destroy obsolete=%s'
-                      % (rm_obsolete,))
-
-            if invalidate_cache:
-                log.debug('invalidating all repositories cache')
-                for repo in Repository.get_all():
-                    ScmModel().mark_for_invalidation(repo.repo_name)
-
-            filesystem_repos = ScmModel().repo_scan()
-            added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
-            _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
-            h.flash(_('Repositories successfully '
-                      'rescanned added: %s ; removed: %s') %
-                    (_repr(added), _repr(removed)),
-                    category='success')
-
-        if setting_id == 'whoosh':
-            repo_location = self._get_hg_ui_settings()['paths_root_path']
-            full_index = request.POST.get('full_index', False)
-            run_task(tasks.whoosh_index, repo_location, full_index)
-            h.flash(_('Whoosh reindex task scheduled'), category='success')
-
-        if setting_id == 'global':
-
-            application_form = ApplicationSettingsForm()()
-            try:
-                form_result = application_form.to_python(dict(request.POST))
-            except formencode.Invalid, errors:
-                return htmlfill.render(
-                     render('admin/settings/settings.html'),
-                     defaults=errors.value,
-                     errors=errors.error_dict or {},
-                     prefix_error=False,
-                     encoding="UTF-8"
-                )
-
-            try:
-                sett1 = RhodeCodeSetting.get_by_name_or_create('title')
-                sett1.app_settings_value = form_result['rhodecode_title']
-                Session().add(sett1)
-
-                sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
-                sett2.app_settings_value = form_result['rhodecode_realm']
-                Session().add(sett2)
-
-                sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
-                sett3.app_settings_value = form_result['rhodecode_ga_code']
-                Session().add(sett3)
-
-                Session().commit()
-                set_rhodecode_config(config)
-                h.flash(_('Updated application settings'), category='success')
-
-            except Exception:
-                log.error(traceback.format_exc())
-                h.flash(_('Error occurred during updating '
-                          'application settings'),
-                          category='error')
-
-        if setting_id == 'visual':
-
-            application_form = ApplicationVisualisationForm()()
-            try:
-                form_result = application_form.to_python(dict(request.POST))
-            except formencode.Invalid, errors:
-                return htmlfill.render(
-                     render('admin/settings/settings.html'),
-                     defaults=errors.value,
-                     errors=errors.error_dict or {},
-                     prefix_error=False,
-                     encoding="UTF-8"
-                )
-
-            try:
-                #TODO: rewrite this to something less ugly
-                sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
-                sett1.app_settings_value = \
-                    form_result['rhodecode_show_public_icon']
-                Session().add(sett1)
-
-                sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
-                sett2.app_settings_value = \
-                    form_result['rhodecode_show_private_icon']
-                Session().add(sett2)
-
-                sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
-                sett3.app_settings_value = \
-                    form_result['rhodecode_stylify_metatags']
-                Session().add(sett3)
-
-                sett4 = RhodeCodeSetting.get_by_name_or_create('repository_fields')
-                sett4.app_settings_value = \
-                    form_result['rhodecode_repository_fields']
-                Session().add(sett4)
-
-                sett5 = RhodeCodeSetting.get_by_name_or_create('dashboard_items')
-                sett5.app_settings_value = \
-                    form_result['rhodecode_dashboard_items']
-                Session().add(sett5)
-
-                sett6 = RhodeCodeSetting.get_by_name_or_create('show_version')
-                sett6.app_settings_value = \
-                    form_result['rhodecode_show_version']
-                Session().add(sett6)
-
-                Session().commit()
-                set_rhodecode_config(config)
-                h.flash(_('Updated visualisation settings'),
-                        category='success')
-
-            except Exception:
-                log.error(traceback.format_exc())
-                h.flash(_('Error occurred during updating '
-                          'visualisation settings'),
-                        category='error')
-
-        if setting_id == 'vcs':
+    def settings_vcs(self):
+        """GET /admin/settings: All items in the collection"""
+        # url('admin_settings')
+        c.active = 'vcs'
+        if request.POST:
             application_form = ApplicationUiSettingsForm()()
             try:
                 form_result = application_form.to_python(dict(request.POST))
@@ -290,7 +151,7 @@
                 sett.ui_active = form_result['extensions_hgsubversion']
                 if sett.ui_active:
                     try:
-                        import hgsubversion
+                        import hgsubversion  # pragma: no cover
                     except ImportError:
                         raise HgsubversionImportError
                 Session().add(sett)
@@ -320,16 +181,225 @@
                 h.flash(_('Error occurred during updating '
                           'application settings'), category='error')
 
-        if setting_id == 'hooks':
+        defaults = RhodeCodeSetting.get_app_settings()
+        defaults.update(self._get_hg_ui_settings())
+
+        return htmlfill.render(
+            render('admin/settings/settings.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
+
+    @HasPermissionAllDecorator('hg.admin')
+    def settings_mapping(self):
+        """GET /admin/settings/mapping: All items in the collection"""
+        # url('admin_settings_mapping')
+        c.active = 'mapping'
+        if request.POST:
+            rm_obsolete = request.POST.get('destroy', False)
+            install_git_hooks = request.POST.get('hooks', False)
+            invalidate_cache = request.POST.get('invalidate', False)
+            log.debug('rescanning repo location with destroy obsolete=%s and '
+                      'install git hooks=%s' % (rm_obsolete,install_git_hooks))
+
+            if invalidate_cache:
+                log.debug('invalidating all repositories cache')
+                for repo in Repository.get_all():
+                    ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
+
+            filesystem_repos = ScmModel().repo_scan()
+            added, removed = repo2db_mapper(filesystem_repos, rm_obsolete,
+                                            install_git_hook=install_git_hooks)
+            _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
+            h.flash(_('Repositories successfully '
+                      'rescanned added: %s ; removed: %s') %
+                    (_repr(added), _repr(removed)),
+                    category='success')
+            return redirect(url('admin_settings_mapping'))
+
+        defaults = RhodeCodeSetting.get_app_settings()
+        defaults.update(self._get_hg_ui_settings())
+
+        return htmlfill.render(
+            render('admin/settings/settings.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
+
+    @HasPermissionAllDecorator('hg.admin')
+    def settings_global(self):
+        """GET /admin/settings/global: All items in the collection"""
+        # url('admin_settings_global')
+        c.active = 'global'
+        if request.POST:
+            application_form = ApplicationSettingsForm()()
+            try:
+                form_result = application_form.to_python(dict(request.POST))
+            except formencode.Invalid, errors:
+                return htmlfill.render(
+                    render('admin/settings/settings.html'),
+                    defaults=errors.value,
+                    errors=errors.error_dict or {},
+                    prefix_error=False,
+                    encoding="UTF-8")
+
+            try:
+                sett1 = RhodeCodeSetting.create_or_update('title',
+                                            form_result['rhodecode_title'])
+                Session().add(sett1)
+
+                sett2 = RhodeCodeSetting.create_or_update('realm',
+                                            form_result['rhodecode_realm'])
+                Session().add(sett2)
+
+                sett3 = RhodeCodeSetting.create_or_update('ga_code',
+                                            form_result['rhodecode_ga_code'])
+                Session().add(sett3)
+
+                sett4 = RhodeCodeSetting.create_or_update('captcha_public_key',
+                                    form_result['rhodecode_captcha_public_key'])
+                Session().add(sett4)
+
+                sett5 = RhodeCodeSetting.create_or_update('captcha_private_key',
+                                    form_result['rhodecode_captcha_private_key'])
+                Session().add(sett5)
+
+                Session().commit()
+                set_rhodecode_config(config)
+                h.flash(_('Updated application settings'), category='success')
+
+            except Exception:
+                log.error(traceback.format_exc())
+                h.flash(_('Error occurred during updating '
+                          'application settings'),
+                          category='error')
+
+            return redirect(url('admin_settings_global'))
+
+        defaults = RhodeCodeSetting.get_app_settings()
+        defaults.update(self._get_hg_ui_settings())
+
+        return htmlfill.render(
+            render('admin/settings/settings.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
+
+    @HasPermissionAllDecorator('hg.admin')
+    def settings_visual(self):
+        """GET /admin/settings/visual: All items in the collection"""
+        # url('admin_settings_visual')
+        c.active = 'visual'
+        if request.POST:
+            application_form = ApplicationVisualisationForm()()
+            try:
+                form_result = application_form.to_python(dict(request.POST))
+            except formencode.Invalid, errors:
+                return htmlfill.render(
+                    render('admin/settings/settings.html'),
+                    defaults=errors.value,
+                    errors=errors.error_dict or {},
+                    prefix_error=False,
+                    encoding="UTF-8"
+                )
+
+            try:
+                settings = [
+                    ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
+                    ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
+                    ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
+                    ('repository_fields', 'rhodecode_repository_fields', 'bool'),
+                    ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
+                    ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
+                    ('show_version', 'rhodecode_show_version', 'bool'),
+                    ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
+                    ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
+                    ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
+                ]
+                for setting, form_key, type_ in settings:
+                    sett = RhodeCodeSetting.create_or_update(setting,
+                                        form_result[form_key], type_)
+                    Session().add(sett)
+
+                Session().commit()
+                set_rhodecode_config(config)
+                h.flash(_('Updated visualisation settings'),
+                        category='success')
+
+            except Exception:
+                log.error(traceback.format_exc())
+                h.flash(_('Error occurred during updating '
+                          'visualisation settings'),
+                        category='error')
+
+            return redirect(url('admin_settings_visual'))
+
+        defaults = RhodeCodeSetting.get_app_settings()
+        defaults.update(self._get_hg_ui_settings())
+
+        return htmlfill.render(
+            render('admin/settings/settings.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
+
+    @HasPermissionAllDecorator('hg.admin')
+    def settings_email(self):
+        """GET /admin/settings/email: All items in the collection"""
+        # url('admin_settings_email')
+        c.active = 'email'
+        if request.POST:
+            test_email = request.POST.get('test_email')
+            test_email_subj = 'RhodeCode test email'
+            test_email_body = ('RhodeCode Email test, '
+                               'RhodeCode version: %s' % c.rhodecode_version)
+            if not test_email:
+                h.flash(_('Please enter email address'), category='error')
+                return redirect(url('admin_settings_email'))
+
+            test_email_html_body = EmailNotificationModel()\
+                .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
+                                body=test_email_body)
+
+            recipients = [test_email] if test_email else None
+
+            run_task(tasks.send_email, recipients, test_email_subj,
+                     test_email_body, test_email_html_body)
+
+            h.flash(_('Send email task created'), category='success')
+            return redirect(url('admin_settings_email'))
+
+        defaults = RhodeCodeSetting.get_app_settings()
+        defaults.update(self._get_hg_ui_settings())
+
+        import rhodecode
+        c.rhodecode_ini = rhodecode.CONFIG
+
+        return htmlfill.render(
+            render('admin/settings/settings.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
+
+    @HasPermissionAllDecorator('hg.admin')
+    def settings_hooks(self):
+        """GET /admin/settings/hooks: All items in the collection"""
+        # url('admin_settings_hooks')
+        c.active = 'hooks'
+        if request.POST:
             if c.visual.allow_custom_hooks_settings:
                 ui_key = request.POST.get('new_hook_ui_key')
                 ui_value = request.POST.get('new_hook_ui_value')
+
+                hook_id = request.POST.get('hook_id')
+
                 try:
-
                     if ui_value and ui_key:
                         RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
-                        h.flash(_('Added new hook'),
-                                category='success')
+                        h.flash(_('Added new hook'), category='success')
+                    elif hook_id:
+                        RhodeCodeUi.delete(hook_id)
+                        Session().commit()
 
                     # check for edits
                     update = False
@@ -347,190 +417,163 @@
                     h.flash(_('Error occurred during hook creation'),
                             category='error')
 
-            return redirect(url('admin_edit_setting', setting_id='hooks'))
-
-        if setting_id == 'email':
-            test_email = request.POST.get('test_email')
-            test_email_subj = 'RhodeCode TestEmail'
-            test_email_body = 'RhodeCode Email test'
-
-            test_email_html_body = EmailNotificationModel()\
-                .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
-                                body=test_email_body)
-
-            recipients = [test_email] if test_email else None
-
-            run_task(tasks.send_email, recipients, test_email_subj,
-                     test_email_body, test_email_html_body)
-
-            h.flash(_('Email task created'), category='success')
-        return redirect(url('admin_settings'))
+                return redirect(url('admin_settings_hooks'))
 
-    @HasPermissionAllDecorator('hg.admin')
-    def delete(self, setting_id):
-        """DELETE /admin/settings/setting_id: Delete an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="DELETE" />
-        # Or using helpers:
-        #    h.form(url('admin_setting', setting_id=ID),
-        #           method='delete')
-        # url('admin_setting', setting_id=ID)
-        if setting_id == 'hooks':
-            hook_id = request.POST.get('hook_id')
-            RhodeCodeUi.delete(hook_id)
-            Session().commit()
-
-    @HasPermissionAllDecorator('hg.admin')
-    def show(self, setting_id, format='html'):
-        """
-        GET /admin/settings/setting_id: Show a specific item"""
-        # url('admin_setting', setting_id=ID)
-
-    @HasPermissionAllDecorator('hg.admin')
-    def edit(self, setting_id, format='html'):
-        """
-        GET /admin/settings/setting_id/edit: Form to
-        edit an existing item"""
-        # url('admin_edit_setting', setting_id=ID)
+        defaults = RhodeCodeSetting.get_app_settings()
+        defaults.update(self._get_hg_ui_settings())
 
         c.hooks = RhodeCodeUi.get_builtin_hooks()
         c.custom_hooks = RhodeCodeUi.get_custom_hooks()
 
         return htmlfill.render(
-            render('admin/settings/hooks.html'),
-            defaults={},
+            render('admin/settings/settings.html'),
+            defaults=defaults,
             encoding="UTF-8",
-            force_defaults=False
-        )
+            force_defaults=False)
 
-    def _load_my_repos_data(self):
-        repos_list = Session().query(Repository)\
-                     .filter(Repository.user_id ==
-                             self.rhodecode_user.user_id)\
-                     .order_by(func.lower(Repository.repo_name)).all()
+    @HasPermissionAllDecorator('hg.admin')
+    def settings_search(self):
+        """GET /admin/settings/search: All items in the collection"""
+        # url('admin_settings_search')
+        c.active = 'search'
+        if request.POST:
+            repo_location = self._get_hg_ui_settings()['paths_root_path']
+            full_index = request.POST.get('full_index', False)
+            run_task(tasks.whoosh_index, repo_location, full_index)
+            h.flash(_('Whoosh reindex task scheduled'), category='success')
+            return redirect(url('admin_settings_search'))
 
-        repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
-                                                   admin=True)
-        #json used to render the grid
-        return json.dumps(repos_data)
+        defaults = RhodeCodeSetting.get_app_settings()
+        defaults.update(self._get_hg_ui_settings())
 
-    @NotAnonymous()
-    def my_account(self):
-        """
-        GET /_admin/my_account Displays info about my account
-        """
-        # url('admin_settings_my_account')
+        return htmlfill.render(
+            render('admin/settings/settings.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
 
-        c.user = User.get(self.rhodecode_user.user_id)
-        c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id,
-                               ip_addr=self.ip_addr)
-        c.ldap_dn = c.user.ldap_dn
+    @HasPermissionAllDecorator('hg.admin')
+    def settings_system(self):
+        """GET /admin/settings/system: All items in the collection"""
+        # url('admin_settings_system')
+        c.active = 'system'
 
-        if c.user.username == 'default':
-            h.flash(_("You can't edit this user since it's"
-              " crucial for entire application"), category='warning')
-            return redirect(url('users'))
+        defaults = RhodeCodeSetting.get_app_settings()
+        defaults.update(self._get_hg_ui_settings())
 
-        #json used to render the grid
-        c.data = self._load_my_repos_data()
+        import rhodecode
+        c.rhodecode_ini = rhodecode.CONFIG
+        c.rhodecode_update_url = defaults.get('rhodecode_update_url')
+        server_info = RhodeCodeSetting.get_server_info()
+        for key, val in server_info.iteritems():
+            setattr(c, key, val)
 
-        defaults = c.user.get_dict()
-
-        c.form = htmlfill.render(
-            render('admin/users/user_edit_my_account_form.html'),
+        return htmlfill.render(
+            render('admin/settings/settings.html'),
             defaults=defaults,
             encoding="UTF-8",
-            force_defaults=False
-        )
-        return render('admin/users/user_edit_my_account.html')
+            force_defaults=False)
 
-    @NotAnonymous()
-    def my_account_update(self):
-        """PUT /_admin/my_account_update: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('admin_settings_my_account_update'),
-        #           method='put')
-        # url('admin_settings_my_account_update', id=ID)
-        uid = self.rhodecode_user.user_id
-        c.user = User.get(self.rhodecode_user.user_id)
-        c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id,
-                               ip_addr=self.ip_addr)
-        c.ldap_dn = c.user.ldap_dn
-        email = self.rhodecode_user.email
-        _form = UserForm(edit=True,
-                         old_data={'user_id': uid, 'email': email})()
-        form_result = {}
+    @HasPermissionAllDecorator('hg.admin')
+    def settings_system_update(self):
+        """GET /admin/settings/system/updates: All items in the collection"""
+        # url('admin_settings_system_update')
+        import json
+        import urllib2
+        from rhodecode.lib.verlib import NormalizedVersion
+        from rhodecode import __version__
+
+        defaults = RhodeCodeSetting.get_app_settings()
+        defaults.update(self._get_hg_ui_settings())
+        _update_url = defaults.get('rhodecode_update_url', '')
+
+        _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
         try:
-            form_result = _form.to_python(dict(request.POST))
-            skip_attrs = ['admin', 'active']  # skip attr for my account
-            if c.ldap_dn:
-                #forbid updating username for ldap accounts
-                skip_attrs.append('username')
-            UserModel().update(uid, form_result, skip_attrs=skip_attrs)
-            h.flash(_('Your account was updated successfully'),
-                    category='success')
-            Session().commit()
-        except formencode.Invalid, errors:
-            #json used to render the grid
-            c.data = self._load_my_repos_data()
-            c.form = htmlfill.render(
-                render('admin/users/user_edit_my_account_form.html'),
-                defaults=errors.value,
-                errors=errors.error_dict or {},
-                prefix_error=False,
-                encoding="UTF-8")
-            return render('admin/users/user_edit_my_account.html')
-        except Exception:
+            import rhodecode
+            ver = rhodecode.__version__
+            log.debug('Checking for upgrade on `%s` server' % _update_url)
+            opener = urllib2.build_opener()
+            opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
+            response = opener.open(_update_url)
+            response_data = response.read()
+            data = json.loads(response_data)
+        except urllib2.URLError, e:
+            log.error(traceback.format_exc())
+            return _err('Failed to contact upgrade server: %r' % e)
+        except ValueError, e:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of user %s') \
-                    % form_result.get('username'), category='error')
+            return _err('Bad data sent from update server')
+
+        latest = data['versions'][0]
+
+        c.update_url = _update_url
+        c.latest_data = latest
+        c.latest_ver = latest['version']
+        c.cur_ver = __version__
+        c.should_upgrade = False
+
+        if NormalizedVersion(c.latest_ver) > NormalizedVersion(c.cur_ver):
+            c.should_upgrade = True
+        c.important_notices = latest['general']
+
+        return render('admin/settings/settings_system_update.html'),
 
-        return redirect(url('my_account'))
-
-    @NotAnonymous()
-    def my_account_my_pullrequests(self):
-        c.show_closed = request.GET.get('pr_show_closed')
-
-        def _filter(pr):
-            s = sorted(pr, key=lambda o: o.created_on, reverse=True)
-            if not c.show_closed:
-                s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
-            return s
+    @HasPermissionAllDecorator('hg.admin')
+    def settings_license(self):
+        """GET /admin/settings/hooks: All items in the collection"""
+        # url('admin_settings_license')
+        c.active = 'license'
+        if request.POST:
+            form_result = request.POST
+            try:
+                sett1 = RhodeCodeSetting.create_or_update('license_key',
+                                    form_result['rhodecode_license_key'],
+                                    'unicode')
+                Session().add(sett1)
+                Session().commit()
+                set_rhodecode_config(config)
+                h.flash(_('Updated license information'),
+                        category='success')
 
-        c.my_pull_requests = _filter(PullRequest.query()\
-                                .filter(PullRequest.user_id ==
-                                        self.rhodecode_user.user_id)\
-                                .all())
+            except Exception:
+                log.error(traceback.format_exc())
+                h.flash(_('Error occurred during updating license info'),
+                        category='error')
 
-        c.participate_in_pull_requests = _filter([
-                    x.pull_request for x in PullRequestReviewers.query()\
-                    .filter(PullRequestReviewers.user_id ==
-                            self.rhodecode_user.user_id).all()])
+            return redirect(url('admin_settings_license'))
 
-        return render('admin/users/user_edit_my_account_pullrequests.html')
+        defaults = RhodeCodeSetting.get_app_settings()
+        defaults.update(self._get_hg_ui_settings())
 
-    def _get_hg_ui_settings(self):
-        ret = RhodeCodeUi.query().all()
-
-        if not ret:
-            raise Exception('Could not get application ui settings !')
-        settings = {}
-        for each in ret:
-            k = each.ui_key
-            v = each.ui_value
-            if k == '/':
-                k = 'root_path'
+        import rhodecode
+        c.rhodecode_ini = rhodecode.CONFIG
+        c.license_token = c.rhodecode_ini.get('license_token')
+        c.generated_license_token = LicenseModel.generate_license_token()
+        c.license_info = {}
+        c.license_loaded = False
+        # try to read info about license
+        try:
+            license_key = defaults.get('rhodecode_license_key')
+            if c.license_token and license_key:
+                c.license_info = json.loads(
+                    LicenseModel(key=c.license_token).decrypt(license_key))
+                expires = h.fmt_date(h.time_to_datetime(c.license_info['valid_till']))
+                now = time.time()
+                if 0 < (c.license_info['valid_till'] - now) < 60*60*24*7:
+                    h.flash(_('Your license will expire on %s, please contact '
+                              'support to extend your license.' % expires), category='warning')
+                if c.license_info['valid_till'] - now < 0:
+                    h.flash(_('Your license has expired on %s, please contact '
+                              'support to extend your license.' % expires), category='error')
+                c.license_loaded = True
+        except Exception, e:
+            log.error(traceback.format_exc())
+            h.flash(_('Unexpected error while reading license key. Please '
+                      'make sure your license token and key are correct'),
+                    category='error')
 
-            if k == 'push_ssl':
-                v = str2bool(v)
-
-            if k.find('.') != -1:
-                k = k.replace('.', '_')
-
-            if each.ui_section in ['hooks', 'extensions']:
-                v = each.ui_active
-
-            settings[each.ui_section + '_' + k] = v
-        return settings
+        return htmlfill.render(
+            render('admin/settings/settings.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/controllers/admin/user_groups.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,461 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.users_groups
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+User Groups crud controller for pylons
+
+:created_on: Jan 25, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import logging
+import traceback
+import formencode
+
+from formencode import htmlfill
+from pylons import request, session, tmpl_context as c, url, config
+from pylons.controllers.util import abort, redirect
+from pylons.i18n.translation import _
+
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import func
+from webob.exc import HTTPInternalServerError
+
+import rhodecode
+from rhodecode.lib import helpers as h
+from rhodecode.lib.exceptions import UserGroupsAssignedException,\
+    RepoGroupAssignmentError
+from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
+from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator,\
+    HasUserGroupPermissionAnyDecorator, HasPermissionAnyDecorator
+from rhodecode.lib.base import BaseController, render
+from rhodecode.model.scm import UserGroupList
+from rhodecode.model.user_group import UserGroupModel
+from rhodecode.model.repo import RepoModel
+from rhodecode.model.db import User, UserGroup, UserGroupToPerm,\
+    UserGroupRepoToPerm, UserGroupRepoGroupToPerm
+from rhodecode.model.forms import UserGroupForm, UserGroupPermsForm,\
+    CustomDefaultPermissionsForm
+from rhodecode.model.meta import Session
+from rhodecode.lib.utils import action_logger
+from rhodecode.lib.compat import json
+
+log = logging.getLogger(__name__)
+
+
+class UserGroupsController(BaseController):
+    """REST Controller styled on the Atom Publishing Protocol"""
+
+    @LoginRequired()
+    def __before__(self):
+        super(UserGroupsController, self).__before__()
+        c.available_permissions = config['available_permissions']
+
+    def __load_data(self, user_group_id):
+        c.group_members_obj = sorted((x.user for x in c.user_group.members),
+                                     key=lambda u: u.username.lower())
+
+        c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
+        c.available_members = sorted(((x.user_id, x.username) for x in
+                                      User.query().all()),
+                                     key=lambda u: u[1].lower())
+
+    def __load_defaults(self, user_group_id):
+        """
+        Load defaults settings for edit, and update
+
+        :param user_group_id:
+        """
+        user_group = UserGroup.get_or_404(user_group_id)
+        data = user_group.get_dict()
+        return data
+
+    def index(self, format='html'):
+        """GET /users_groups: All items in the collection"""
+        # url('users_groups')
+        _list = UserGroup.query()\
+                        .order_by(func.lower(UserGroup.users_group_name))\
+                        .all()
+        group_iter = UserGroupList(_list, perm_set=['usergroup.admin'])
+        user_groups_data = []
+        total_records = len(group_iter)
+        _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
+        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
+
+        user_group_name = lambda user_group_id, user_group_name: (
+            template.get_def("user_group_name")
+            .render(user_group_id, user_group_name, _=_, h=h, c=c)
+        )
+        user_group_actions = lambda user_group_id, user_group_name: (
+            template.get_def("user_group_actions")
+            .render(user_group_id, user_group_name, _=_, h=h, c=c)
+        )
+        for user_gr in group_iter:
+
+            user_groups_data.append({
+                "raw_name": user_gr.users_group_name,
+                "group_name": user_group_name(user_gr.users_group_id,
+                                              user_gr.users_group_name),
+                "desc": user_gr.user_group_description,
+                "members": len(user_gr.members),
+                "active": h.boolicon(user_gr.users_group_active),
+                "owner": h.person(user_gr.user.username),
+                "action": user_group_actions(user_gr.users_group_id, user_gr.users_group_name)
+            })
+
+        c.data = json.dumps({
+            "totalRecords": total_records,
+            "startIndex": 0,
+            "sort": None,
+            "dir": "asc",
+            "records": user_groups_data
+        })
+
+        return render('admin/user_groups/user_groups.html')
+
+    @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
+    def create(self):
+        """POST /users_groups: Create a new item"""
+        # url('users_groups')
+
+        users_group_form = UserGroupForm()()
+        try:
+            form_result = users_group_form.to_python(dict(request.POST))
+            UserGroupModel().create(name=form_result['users_group_name'],
+                                    description=form_result['user_group_description'],
+                                    owner=self.rhodecode_user.user_id,
+                                    active=form_result['users_group_active'])
+
+            gr = form_result['users_group_name']
+            action_logger(self.rhodecode_user,
+                          'admin_created_users_group:%s' % gr,
+                          None, self.ip_addr, self.sa)
+            h.flash(_('Created user group %s') % gr, category='success')
+            Session().commit()
+        except formencode.Invalid, errors:
+            return htmlfill.render(
+                render('admin/user_groups/user_group_add.html'),
+                defaults=errors.value,
+                errors=errors.error_dict or {},
+                prefix_error=False,
+                encoding="UTF-8")
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('Error occurred during creation of user group %s') \
+                    % request.POST.get('users_group_name'), category='error')
+
+        return redirect(url('users_groups'))
+
+    @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
+    def new(self, format='html'):
+        """GET /user_groups/new: Form to create a new item"""
+        # url('new_users_group')
+        return render('admin/user_groups/user_group_add.html')
+
+    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    def update(self, id):
+        """PUT /user_groups/id: Update an existing item"""
+        # Forms posted to this method should contain a hidden field:
+        #    <input type="hidden" name="_method" value="PUT" />
+        # Or using helpers:
+        #    h.form(url('users_group', id=ID),
+        #           method='put')
+        # url('users_group', id=ID)
+
+        c.user_group = UserGroup.get_or_404(id)
+        c.active = 'settings'
+        self.__load_data(id)
+
+        available_members = [safe_unicode(x[0]) for x in c.available_members]
+
+        users_group_form = UserGroupForm(edit=True,
+                                         old_data=c.user_group.get_dict(),
+                                         available_members=available_members)()
+
+        try:
+            form_result = users_group_form.to_python(request.POST)
+            UserGroupModel().update(c.user_group, form_result)
+            gr = form_result['users_group_name']
+            action_logger(self.rhodecode_user,
+                          'admin_updated_users_group:%s' % gr,
+                          None, self.ip_addr, self.sa)
+            h.flash(_('Updated user group %s') % gr, category='success')
+            Session().commit()
+        except formencode.Invalid, errors:
+            ug_model = UserGroupModel()
+            defaults = errors.value
+            e = errors.error_dict or {}
+            defaults.update({
+                'create_repo_perm': ug_model.has_perm(id,
+                                                      'hg.create.repository'),
+                'fork_repo_perm': ug_model.has_perm(id,
+                                                    'hg.fork.repository'),
+                '_method': 'put'
+            })
+
+            return htmlfill.render(
+                render('admin/user_groups/user_group_edit.html'),
+                defaults=defaults,
+                errors=e,
+                prefix_error=False,
+                encoding="UTF-8")
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('Error occurred during update of user group %s') \
+                    % request.POST.get('users_group_name'), category='error')
+
+        return redirect(url('edit_users_group', id=id))
+
+    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    def delete(self, id):
+        """DELETE /user_groups/id: Delete an existing item"""
+        # Forms posted to this method should contain a hidden field:
+        #    <input type="hidden" name="_method" value="DELETE" />
+        # Or using helpers:
+        #    h.form(url('users_group', id=ID),
+        #           method='delete')
+        # url('users_group', id=ID)
+        usr_gr = UserGroup.get_or_404(id)
+        try:
+            UserGroupModel().delete(usr_gr)
+            Session().commit()
+            h.flash(_('Successfully deleted user group'), category='success')
+        except UserGroupsAssignedException, e:
+            h.flash(e, category='error')
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('An error occurred during deletion of user group'),
+                    category='error')
+        return redirect(url('users_groups'))
+
+    def show(self, id, format='html'):
+        """GET /user_groups/id: Show a specific item"""
+        # url('users_group', id=ID)
+
+    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    def edit(self, id, format='html'):
+        """GET /user_groups/id/edit: Form to edit an existing item"""
+        # url('edit_users_group', id=ID)
+
+        c.user_group = UserGroup.get_or_404(id)
+        c.active = 'settings'
+        self.__load_data(id)
+
+        defaults = self.__load_defaults(id)
+
+        return htmlfill.render(
+            render('admin/user_groups/user_group_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False
+        )
+
+    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    def edit_perms(self, id):
+        c.user_group = UserGroup.get_or_404(id)
+        c.active = 'perms'
+
+        repo_model = RepoModel()
+        c.users_array = repo_model.get_users_js()
+        c.user_groups_array = repo_model.get_user_groups_js()
+
+        defaults = {}
+        # fill user group users
+        for p in c.user_group.user_user_group_to_perm:
+            defaults.update({'u_perm_%s' % p.user.username:
+                             p.permission.permission_name})
+
+        for p in c.user_group.user_group_user_group_to_perm:
+            defaults.update({'g_perm_%s' % p.user_group.users_group_name:
+                             p.permission.permission_name})
+
+        return htmlfill.render(
+            render('admin/user_groups/user_group_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False
+        )
+
+    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    def update_perms(self, id):
+        """
+        grant permission for given usergroup
+
+        :param id:
+        """
+        user_group = UserGroup.get_or_404(id)
+        form = UserGroupPermsForm()().to_python(request.POST)
+
+        # set the permissions !
+        try:
+            UserGroupModel()._update_permissions(user_group, form['perms_new'],
+                                                 form['perms_updates'])
+        except RepoGroupAssignmentError:
+            h.flash(_('Target group cannot be the same'), category='error')
+            return redirect(url('edit_user_group_perms', id=id))
+        #TODO: implement this
+        #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions',
+        #              repo_name, self.ip_addr, self.sa)
+        Session().commit()
+        h.flash(_('User Group permissions updated'), category='success')
+        return redirect(url('edit_user_group_perms', id=id))
+
+    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    def delete_perms(self, id):
+        """
+        DELETE an existing repository group permission user
+
+        :param group_name:
+        """
+        try:
+            obj_type = request.POST.get('obj_type')
+            obj_id = None
+            if obj_type == 'user':
+                obj_id = safe_int(request.POST.get('user_id'))
+            elif obj_type == 'user_group':
+                obj_id = safe_int(request.POST.get('user_group_id'))
+
+            if not c.rhodecode_user.is_admin:
+                if obj_type == 'user' and c.rhodecode_user.user_id == obj_id:
+                    msg = _('Cannot revoke permission for yourself as admin')
+                    h.flash(msg, category='warning')
+                    raise Exception('revoke admin permission on self')
+            if obj_type == 'user':
+                UserGroupModel().revoke_user_permission(user_group=id,
+                                                        user=obj_id)
+            elif obj_type == 'user_group':
+                UserGroupModel().revoke_user_group_permission(target_user_group=id,
+                                                              user_group=obj_id)
+            Session().commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('An error occurred during revoking of permission'),
+                    category='error')
+            raise HTTPInternalServerError()
+
+    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    def edit_default_perms(self, id):
+        c.user_group = UserGroup.get_or_404(id)
+        c.active = 'default_perms'
+
+        permissions = {
+            'repositories': {},
+            'repositories_groups': {}
+        }
+        ugroup_repo_perms = UserGroupRepoToPerm.query()\
+            .options(joinedload(UserGroupRepoToPerm.permission))\
+            .options(joinedload(UserGroupRepoToPerm.repository))\
+            .filter(UserGroupRepoToPerm.users_group_id == id)\
+            .all()
+
+        for gr in ugroup_repo_perms:
+            permissions['repositories'][gr.repository.repo_name]  \
+                = gr.permission.permission_name
+
+        ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
+            .options(joinedload(UserGroupRepoGroupToPerm.permission))\
+            .options(joinedload(UserGroupRepoGroupToPerm.group))\
+            .filter(UserGroupRepoGroupToPerm.users_group_id == id)\
+            .all()
+
+        for gr in ugroup_group_perms:
+            permissions['repositories_groups'][gr.group.group_name] \
+                = gr.permission.permission_name
+        c.permissions = permissions
+
+        ug_model = UserGroupModel()
+
+        defaults = c.user_group.get_dict()
+        defaults.update({
+            'create_repo_perm': ug_model.has_perm(c.user_group,
+                                                  'hg.create.repository'),
+            'create_user_group_perm': ug_model.has_perm(c.user_group,
+                                                        'hg.usergroup.create.true'),
+            'fork_repo_perm': ug_model.has_perm(c.user_group,
+                                                'hg.fork.repository'),
+        })
+
+        return htmlfill.render(
+            render('admin/user_groups/user_group_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False
+        )
+
+    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    def update_default_perms(self, id):
+        """PUT /users_perm/id: Update an existing item"""
+        # url('users_group_perm', id=ID, method='put')
+
+        user_group = UserGroup.get_or_404(id)
+
+        try:
+            form = CustomDefaultPermissionsForm()()
+            form_result = form.to_python(request.POST)
+
+            inherit_perms = form_result['inherit_default_permissions']
+            user_group.inherit_default_permissions = inherit_perms
+            Session().add(user_group)
+            usergroup_model = UserGroupModel()
+
+            defs = UserGroupToPerm.query()\
+                .filter(UserGroupToPerm.users_group == user_group)\
+                .all()
+            for ug in defs:
+                Session().delete(ug)
+
+            if form_result['create_repo_perm']:
+                usergroup_model.grant_perm(id, 'hg.create.repository')
+            else:
+                usergroup_model.grant_perm(id, 'hg.create.none')
+            if form_result['create_user_group_perm']:
+                usergroup_model.grant_perm(id, 'hg.usergroup.create.true')
+            else:
+                usergroup_model.grant_perm(id, 'hg.usergroup.create.false')
+            if form_result['fork_repo_perm']:
+                usergroup_model.grant_perm(id, 'hg.fork.repository')
+            else:
+                usergroup_model.grant_perm(id, 'hg.fork.none')
+
+            h.flash(_("Updated permissions"), category='success')
+            Session().commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('An error occurred during permissions saving'),
+                    category='error')
+
+        return redirect(url('edit_user_group_default_perms', id=id))
+
+    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    def edit_advanced(self, id):
+        c.user_group = UserGroup.get_or_404(id)
+        c.active = 'advanced'
+        c.group_members_obj = sorted((x.user for x in c.user_group.members),
+                                     key=lambda u: u.username.lower())
+        return render('admin/user_groups/user_group_edit.html')
+
+
+    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    def edit_members(self, id):
+        c.user_group = UserGroup.get_or_404(id)
+        c.active = 'members'
+        c.group_members_obj = sorted((x.user for x in c.user_group.members),
+                                     key=lambda u: u.username.lower())
+
+        c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
+        return render('admin/user_groups/user_group_edit.html')
--- a/rhodecode/controllers/admin/users.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/admin/users.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.admin.users
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Users crud controller for pylons
-
-    :created_on: Apr 4, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.admin.users
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Users crud controller for pylons
+
+:created_on: Apr 4, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import logging
 import traceback
@@ -32,14 +32,18 @@
 from pylons import request, session, tmpl_context as c, url, config
 from pylons.controllers.util import redirect
 from pylons.i18n.translation import _
+from sqlalchemy.sql.expression import func
 
 import rhodecode
 from rhodecode.lib.exceptions import DefaultUserException, \
     UserOwnsReposException, UserCreationError
 from rhodecode.lib import helpers as h
 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
-    AuthUser
+    AuthUser, generate_api_key
+import rhodecode.lib.auth_modules.auth_rhodecode
+from rhodecode.lib import auth_modules
 from rhodecode.lib.base import BaseController, render
+from rhodecode.model.api_key import ApiKeyModel
 
 from rhodecode.model.db import User, UserEmailMap, UserIpMap, UserToPerm
 from rhodecode.model.forms import UserForm, CustomDefaultPermissionsForm
@@ -47,16 +51,13 @@
 from rhodecode.model.meta import Session
 from rhodecode.lib.utils import action_logger
 from rhodecode.lib.compat import json
-from rhodecode.lib.utils2 import datetime_to_time, str2bool
+from rhodecode.lib.utils2 import datetime_to_time, str2bool, safe_int
 
 log = logging.getLogger(__name__)
 
 
 class UsersController(BaseController):
     """REST Controller styled on the Atom Publishing Protocol"""
-    # To properly map this controller, ensure your config/routing.py
-    # file has a resource setup:
-    #     map.resource('user', 'users')
 
     @LoginRequired()
     @HasPermissionAllDecorator('hg.admin')
@@ -70,6 +71,7 @@
 
         c.users_list = User.query().order_by(User.username)\
                         .filter(User.username != User.DEFAULT_USER)\
+                        .order_by(func.lower(User.username))\
                         .all()
 
         users_data = []
@@ -81,7 +83,7 @@
                 template.get_def("user_gravatar")
                 .render(user_email, size, _=_, h=h, c=c))
 
-        user_lnk = lambda user_id, username: (
+        username = lambda user_id, username: (
                 template.get_def("user_name")
                 .render(user_id, username, _=_, h=h, c=c))
 
@@ -92,16 +94,17 @@
         for user in c.users_list:
 
             users_data.append({
-                "gravatar": grav_tmpl(user. email, 24),
-                "raw_username": user.username,
-                "username": user_lnk(user.user_id, user.username),
+                "gravatar": grav_tmpl(user. email, 20),
+                "raw_name": user.username,
+                "username": username(user.user_id, user.username),
                 "firstname": user.name,
                 "lastname": user.lastname,
                 "last_login": h.fmt_date(user.last_login),
                 "last_login_raw": datetime_to_time(user.last_login),
                 "active": h.boolicon(user.active),
                 "admin": h.boolicon(user.admin),
-                "ldap": h.boolicon(bool(user.ldap_dn)),
+                "extern_type": user.extern_type,
+                "extern_name": user.extern_name,
                 "action": user_actions(user.user_id, user.username),
             })
 
@@ -118,7 +121,7 @@
     def create(self):
         """POST /users: Create a new item"""
         # url('users')
-
+        c.default_extern_type = auth_modules.auth_rhodecode.RhodeCodeAuthPlugin.name
         user_model = UserModel()
         user_form = UserForm()()
         try:
@@ -148,6 +151,7 @@
     def new(self, format='html'):
         """GET /users/new: Form to create a new item"""
         # url('new_user')
+        c.default_extern_type = auth_modules.auth_rhodecode.RhodeCodeAuthPlugin.name
         return render('admin/users/user_add.html')
 
     def update(self, id):
@@ -158,19 +162,23 @@
         #    h.form(url('update_user', id=ID),
         #           method='put')
         # url('user', id=ID)
+        c.active = 'profile'
         user_model = UserModel()
         c.user = user_model.get(id)
-        c.ldap_dn = c.user.ldap_dn
+        c.extern_type = c.user.extern_type
+        c.extern_name = c.user.extern_name
         c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
         _form = UserForm(edit=True, old_data={'user_id': id,
                                               'email': c.user.email})()
         form_result = {}
         try:
             form_result = _form.to_python(dict(request.POST))
-            skip_attrs = []
-            if c.ldap_dn:
-                #forbid updating username for ldap accounts
-                skip_attrs = ['username']
+            skip_attrs = ['extern_type', 'extern_name']
+            #TODO: plugin should define if username can be updated
+            if c.extern_type != "rhodecode":
+                # forbid updating username for external accounts
+                skip_attrs.append('username')
+
             user_model.update(id, form_result, skip_attrs=skip_attrs)
             usr = form_result['username']
             action_logger(self.rhodecode_user, 'admin_updated_user:%s' % usr,
@@ -178,14 +186,11 @@
             h.flash(_('User updated successfully'), category='success')
             Session().commit()
         except formencode.Invalid, errors:
-            c.user_email_map = UserEmailMap.query()\
-                            .filter(UserEmailMap.user == c.user).all()
-            c.user_ip_map = UserIpMap.query()\
-                            .filter(UserIpMap.user == c.user).all()
             defaults = errors.value
             e = errors.error_dict or {}
             defaults.update({
-                'create_repo_perm': user_model.has_perm(id, 'hg.create.repository'),
+                'create_repo_perm': user_model.has_perm(id,
+                                                        'hg.create.repository'),
                 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
                 '_method': 'put'
             })
@@ -231,36 +236,131 @@
         """GET /users/id/edit: Form to edit an existing item"""
         # url('edit_user', id=ID)
         c.user = User.get_or_404(id)
+        if c.user.username == User.DEFAULT_USER:
+            h.flash(_("You can't edit this user"), category='warning')
+            return redirect(url('users'))
 
-        if c.user.username == 'default':
+        c.active = 'profile'
+        c.extern_type = c.user.extern_type
+        c.extern_name = c.user.extern_name
+        c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
+
+        defaults = c.user.get_dict()
+        return htmlfill.render(
+            render('admin/users/user_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
+
+    def edit_advanced(self, id):
+        c.user = User.get_or_404(id)
+        if c.user.username == User.DEFAULT_USER:
             h.flash(_("You can't edit this user"), category='warning')
             return redirect(url('users'))
 
+        c.active = 'advanced'
         c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
-        c.user.permissions = {}
-        c.granted_permissions = UserModel().fill_perms(c.user)\
-            .permissions['global']
-        c.user_email_map = UserEmailMap.query()\
-                        .filter(UserEmailMap.user == c.user).all()
-        c.user_ip_map = UserIpMap.query()\
-                        .filter(UserIpMap.user == c.user).all()
+
         umodel = UserModel()
-        c.ldap_dn = c.user.ldap_dn
         defaults = c.user.get_dict()
         defaults.update({
-         'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
-         'create_user_group_perm': umodel.has_perm(c.user, 'hg.usergroup.create.true'),
-         'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
+            'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
+            'create_user_group_perm': umodel.has_perm(c.user,
+                                                      'hg.usergroup.create.true'),
+            'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
         })
+        return htmlfill.render(
+            render('admin/users/user_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
 
+    def edit_api_keys(self, id):
+        c.user = User.get_or_404(id)
+        if c.user.username == User.DEFAULT_USER:
+            h.flash(_("You can't edit this user"), category='warning')
+            return redirect(url('users'))
+
+        c.active = 'api_keys'
+        show_expired = True
+        c.lifetime_values = [
+            (str(-1), _('forever')),
+            (str(5), _('5 minutes')),
+            (str(60), _('1 hour')),
+            (str(60 * 24), _('1 day')),
+            (str(60 * 24 * 30), _('1 month')),
+        ]
+        c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
+        c.user_api_keys = ApiKeyModel().get_api_keys(c.user.user_id,
+                                                     show_expired=show_expired)
+        defaults = c.user.get_dict()
         return htmlfill.render(
             render('admin/users/user_edit.html'),
             defaults=defaults,
             encoding="UTF-8",
-            force_defaults=False
-        )
+            force_defaults=False)
+
+    def add_api_key(self, id):
+        c.user = User.get_or_404(id)
+        if c.user.username == User.DEFAULT_USER:
+            h.flash(_("You can't edit this user"), category='warning')
+            return redirect(url('users'))
+
+        lifetime = safe_int(request.POST.get('lifetime'), -1)
+        description = request.POST.get('description')
+        new_api_key = ApiKeyModel().create(c.user.user_id, description, lifetime)
+        Session().commit()
+        h.flash(_("Api key successfully created"), category='success')
+        return redirect(url('edit_user_api_keys', id=c.user.user_id))
+
+    def delete_api_key(self, id):
+        c.user = User.get_or_404(id)
+        if c.user.username == User.DEFAULT_USER:
+            h.flash(_("You can't edit this user"), category='warning')
+            return redirect(url('users'))
 
-    def update_perm(self, id):
+        api_key = request.POST.get('del_api_key')
+        if request.POST.get('del_api_key_builtin'):
+            user = User.get(c.user.user_id)
+            if user:
+                user.api_key = generate_api_key(user.username)
+                Session().add(user)
+                Session().commit()
+                h.flash(_("Api key successfully reset"), category='success')
+        elif api_key:
+            ApiKeyModel().delete(api_key, c.user.user_id)
+            Session().commit()
+            h.flash(_("Api key successfully deleted"), category='success')
+
+        return redirect(url('edit_user_api_keys', id=c.user.user_id))
+
+    def update_account(self, id):
+        pass
+
+    def edit_perms(self, id):
+        c.user = User.get_or_404(id)
+        if c.user.username == User.DEFAULT_USER:
+            h.flash(_("You can't edit this user"), category='warning')
+            return redirect(url('users'))
+
+        c.active = 'perms'
+        c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
+
+        umodel = UserModel()
+        defaults = c.user.get_dict()
+        defaults.update({
+            'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
+            'create_user_group_perm': umodel.has_perm(c.user,
+                                                      'hg.usergroup.create.true'),
+            'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
+        })
+        return htmlfill.render(
+            render('admin/users/user_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
+
+    def update_perms(self, id):
         """PUT /users_perm/id: Update an existing item"""
         # url('user_perm', id=ID, method='put')
         user = User.get_or_404(id)
@@ -298,7 +398,24 @@
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during permissions saving'),
                     category='error')
-        return redirect(url('edit_user', id=id))
+        return redirect(url('edit_user_perms', id=id))
+
+    def edit_emails(self, id):
+        c.user = User.get_or_404(id)
+        if c.user.username == User.DEFAULT_USER:
+            h.flash(_("You can't edit this user"), category='warning')
+            return redirect(url('users'))
+
+        c.active = 'emails'
+        c.user_email_map = UserEmailMap.query()\
+            .filter(UserEmailMap.user == c.user).all()
+
+        defaults = c.user.get_dict()
+        return htmlfill.render(
+            render('admin/users/user_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
 
     def add_email(self, id):
         """POST /user_emails:Add an existing item"""
@@ -318,16 +435,38 @@
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during email saving'),
                     category='error')
-        return redirect(url('edit_user', id=id))
+        return redirect(url('edit_user_emails', id=id))
 
     def delete_email(self, id):
         """DELETE /user_emails_delete/id: Delete an existing item"""
         # url('user_emails_delete', id=ID, method='delete')
+        email_id = request.POST.get('del_email_id')
         user_model = UserModel()
-        user_model.delete_extra_email(id, request.POST.get('del_email'))
+        user_model.delete_extra_email(id, email_id)
         Session().commit()
         h.flash(_("Removed email from user"), category='success')
-        return redirect(url('edit_user', id=id))
+        return redirect(url('edit_user_emails', id=id))
+
+    def edit_ips(self, id):
+        c.user = User.get_or_404(id)
+        if c.user.username == User.DEFAULT_USER:
+            h.flash(_("You can't edit this user"), category='warning')
+            return redirect(url('users'))
+
+        c.active = 'ips'
+        c.user_ip_map = UserIpMap.query()\
+            .filter(UserIpMap.user == c.user).all()
+
+        c.inherit_default_ips = c.user.inherit_default_permissions
+        c.default_user_ip_map = UserIpMap.query()\
+            .filter(UserIpMap.user == User.get_default_user()).all()
+
+        defaults = c.user.get_dict()
+        return htmlfill.render(
+            render('admin/users/user_edit.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False)
 
     def add_ip(self, id):
         """POST /user_ips:Add an existing item"""
@@ -339,7 +478,7 @@
         try:
             user_model.add_extra_ip(id, ip)
             Session().commit()
-            h.flash(_("Added ip %s to user") % ip, category='success')
+            h.flash(_("Added ip %s to user whitelist") % ip, category='success')
         except formencode.Invalid, error:
             msg = error.error_dict['ip']
             h.flash(msg, category='error')
@@ -347,17 +486,20 @@
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during ip saving'),
                     category='error')
+
         if 'default_user' in request.POST:
-            return redirect(url('edit_permission', id='default'))
-        return redirect(url('edit_user', id=id))
+            return redirect(url('admin_permissions_ips'))
+        return redirect(url('edit_user_ips', id=id))
 
     def delete_ip(self, id):
         """DELETE /user_ips_delete/id: Delete an existing item"""
         # url('user_ips_delete', id=ID, method='delete')
+        ip_id = request.POST.get('del_ip_id')
         user_model = UserModel()
-        user_model.delete_extra_ip(id, request.POST.get('del_ip'))
+        user_model.delete_extra_ip(id, ip_id)
         Session().commit()
-        h.flash(_("Removed ip from user"), category='success')
+        h.flash(_("Removed ip address from user whitelist"), category='success')
+
         if 'default_user' in request.POST:
-            return redirect(url('edit_permission', id='default'))
-        return redirect(url('edit_user', id=id))
+            return redirect(url('admin_permissions_ips'))
+        return redirect(url('edit_user_ips', id=id))
--- a/rhodecode/controllers/admin/users_groups.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,379 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.admin.users_groups
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    User Groups crud controller for pylons
-
-    :created_on: Jan 25, 2011
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# 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 traceback
-import formencode
-
-from formencode import htmlfill
-from pylons import request, session, tmpl_context as c, url, config
-from pylons.controllers.util import abort, redirect
-from pylons.i18n.translation import _
-
-from rhodecode.lib import helpers as h
-from rhodecode.lib.exceptions import UserGroupsAssignedException,\
-    RepoGroupAssignmentError
-from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
-from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator,\
-    HasUserGroupPermissionAnyDecorator, HasPermissionAnyDecorator
-from rhodecode.lib.base import BaseController, render
-from rhodecode.model.scm import UserGroupList
-from rhodecode.model.users_group import UserGroupModel
-from rhodecode.model.repo import RepoModel
-from rhodecode.model.db import User, UserGroup, UserGroupToPerm,\
-    UserGroupRepoToPerm, UserGroupRepoGroupToPerm
-from rhodecode.model.forms import UserGroupForm, UserGroupPermsForm,\
-    CustomDefaultPermissionsForm
-from rhodecode.model.meta import Session
-from rhodecode.lib.utils import action_logger
-from sqlalchemy.orm import joinedload
-from webob.exc import HTTPInternalServerError
-
-log = logging.getLogger(__name__)
-
-
-class UsersGroupsController(BaseController):
-    """REST Controller styled on the Atom Publishing Protocol"""
-    # To properly map this controller, ensure your config/routing.py
-    # file has a resource setup:
-    #     map.resource('users_group', 'users_groups')
-
-    @LoginRequired()
-    def __before__(self):
-        super(UsersGroupsController, self).__before__()
-        c.available_permissions = config['available_permissions']
-
-    def __load_data(self, user_group_id):
-        permissions = {
-            'repositories': {},
-            'repositories_groups': {}
-        }
-        ugroup_repo_perms = UserGroupRepoToPerm.query()\
-            .options(joinedload(UserGroupRepoToPerm.permission))\
-            .options(joinedload(UserGroupRepoToPerm.repository))\
-            .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
-            .all()
-
-        for gr in ugroup_repo_perms:
-            permissions['repositories'][gr.repository.repo_name]  \
-                = gr.permission.permission_name
-
-        ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
-            .options(joinedload(UserGroupRepoGroupToPerm.permission))\
-            .options(joinedload(UserGroupRepoGroupToPerm.group))\
-            .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
-            .all()
-
-        for gr in ugroup_group_perms:
-            permissions['repositories_groups'][gr.group.group_name] \
-                = gr.permission.permission_name
-        c.permissions = permissions
-        c.group_members_obj = sorted((x.user for x in c.users_group.members),
-                                     key=lambda u: u.username.lower())
-
-        c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
-        c.available_members = sorted(((x.user_id, x.username) for x in
-                                      User.query().all()),
-                                     key=lambda u: u[1].lower())
-        repo_model = RepoModel()
-        c.users_array = repo_model.get_users_js()
-        c.users_groups_array = repo_model.get_users_groups_js()
-        c.available_permissions = config['available_permissions']
-
-    def __load_defaults(self, user_group_id):
-        """
-        Load defaults settings for edit, and update
-
-        :param user_group_id:
-        """
-        user_group = UserGroup.get_or_404(user_group_id)
-        data = user_group.get_dict()
-
-        ug_model = UserGroupModel()
-
-        data.update({
-            'create_repo_perm': ug_model.has_perm(user_group,
-                                                  'hg.create.repository'),
-            'create_user_group_perm': ug_model.has_perm(user_group,
-                                                  'hg.usergroup.create.true'),
-            'fork_repo_perm': ug_model.has_perm(user_group,
-                                                'hg.fork.repository'),
-        })
-
-        # fill user group users
-        for p in user_group.user_user_group_to_perm:
-            data.update({'u_perm_%s' % p.user.username:
-                             p.permission.permission_name})
-
-        for p in user_group.user_group_user_group_to_perm:
-            data.update({'g_perm_%s' % p.user_group.users_group_name:
-                             p.permission.permission_name})
-
-        return data
-
-    def index(self, format='html'):
-        """GET /users_groups: All items in the collection"""
-        # url('users_groups')
-
-        group_iter = UserGroupList(UserGroup().query().all(),
-                                   perm_set=['usergroup.admin'])
-        sk = lambda g: g.users_group_name
-        c.users_groups_list = sorted(group_iter, key=sk)
-        return render('admin/users_groups/users_groups.html')
-
-    @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
-    def create(self):
-        """POST /users_groups: Create a new item"""
-        # url('users_groups')
-
-        users_group_form = UserGroupForm()()
-        try:
-            form_result = users_group_form.to_python(dict(request.POST))
-            UserGroupModel().create(name=form_result['users_group_name'],
-                                    owner=self.rhodecode_user.user_id,
-                                    active=form_result['users_group_active'])
-
-            gr = form_result['users_group_name']
-            action_logger(self.rhodecode_user,
-                          'admin_created_users_group:%s' % gr,
-                          None, self.ip_addr, self.sa)
-            h.flash(_('Created user group %s') % gr, category='success')
-            Session().commit()
-        except formencode.Invalid, errors:
-            return htmlfill.render(
-                render('admin/users_groups/users_group_add.html'),
-                defaults=errors.value,
-                errors=errors.error_dict or {},
-                prefix_error=False,
-                encoding="UTF-8")
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('Error occurred during creation of user group %s') \
-                    % request.POST.get('users_group_name'), category='error')
-
-        return redirect(url('users_groups'))
-
-    @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
-    def new(self, format='html'):
-        """GET /users_groups/new: Form to create a new item"""
-        # url('new_users_group')
-        return render('admin/users_groups/users_group_add.html')
-
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
-    def update(self, id):
-        """PUT /users_groups/id: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('users_group', id=ID),
-        #           method='put')
-        # url('users_group', id=ID)
-
-        c.users_group = UserGroup.get_or_404(id)
-        self.__load_data(id)
-
-        available_members = [safe_unicode(x[0]) for x in c.available_members]
-
-        users_group_form = UserGroupForm(edit=True,
-                                          old_data=c.users_group.get_dict(),
-                                          available_members=available_members)()
-
-        try:
-            form_result = users_group_form.to_python(request.POST)
-            UserGroupModel().update(c.users_group, form_result)
-            gr = form_result['users_group_name']
-            action_logger(self.rhodecode_user,
-                          'admin_updated_users_group:%s' % gr,
-                          None, self.ip_addr, self.sa)
-            h.flash(_('Updated user group %s') % gr, category='success')
-            Session().commit()
-        except formencode.Invalid, errors:
-            ug_model = UserGroupModel()
-            defaults = errors.value
-            e = errors.error_dict or {}
-            defaults.update({
-                'create_repo_perm': ug_model.has_perm(id,
-                                                      'hg.create.repository'),
-                'fork_repo_perm': ug_model.has_perm(id,
-                                                    'hg.fork.repository'),
-                '_method': 'put'
-            })
-
-            return htmlfill.render(
-                render('admin/users_groups/users_group_edit.html'),
-                defaults=defaults,
-                errors=e,
-                prefix_error=False,
-                encoding="UTF-8")
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of user group %s') \
-                    % request.POST.get('users_group_name'), category='error')
-
-        return redirect(url('edit_users_group', id=id))
-
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
-    def delete(self, id):
-        """DELETE /users_groups/id: Delete an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="DELETE" />
-        # Or using helpers:
-        #    h.form(url('users_group', id=ID),
-        #           method='delete')
-        # url('users_group', id=ID)
-        usr_gr = UserGroup.get_or_404(id)
-        try:
-            UserGroupModel().delete(usr_gr)
-            Session().commit()
-            h.flash(_('Successfully deleted user group'), category='success')
-        except UserGroupsAssignedException, e:
-            h.flash(e, category='error')
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('An error occurred during deletion of user group'),
-                    category='error')
-        return redirect(url('users_groups'))
-
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
-    def set_user_group_perm_member(self, id):
-        """
-        grant permission for given usergroup
-
-        :param id:
-        """
-        user_group = UserGroup.get_or_404(id)
-        form = UserGroupPermsForm()().to_python(request.POST)
-
-        # set the permissions !
-        try:
-            UserGroupModel()._update_permissions(user_group, form['perms_new'],
-                                                 form['perms_updates'])
-        except RepoGroupAssignmentError:
-            h.flash(_('Target group cannot be the same'), category='error')
-            return redirect(url('edit_users_group', id=id))
-        #TODO: implement this
-        #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions',
-        #              repo_name, self.ip_addr, self.sa)
-        Session().commit()
-        h.flash(_('User Group permissions updated'), category='success')
-        return redirect(url('edit_users_group', id=id))
-
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
-    def delete_user_group_perm_member(self, id):
-        """
-        DELETE an existing repository group permission user
-
-        :param group_name:
-        """
-        try:
-            obj_type = request.POST.get('obj_type')
-            obj_id = None
-            if obj_type == 'user':
-                obj_id = safe_int(request.POST.get('user_id'))
-            elif obj_type == 'user_group':
-                obj_id = safe_int(request.POST.get('user_group_id'))
-
-            if not c.rhodecode_user.is_admin:
-                if obj_type == 'user' and c.rhodecode_user.user_id == obj_id:
-                    msg = _('Cannot revoke permission for yourself as admin')
-                    h.flash(msg, category='warning')
-                    raise Exception('revoke admin permission on self')
-            if obj_type == 'user':
-                UserGroupModel().revoke_user_permission(user_group=id,
-                                                        user=obj_id)
-            elif obj_type == 'user_group':
-                UserGroupModel().revoke_users_group_permission(target_user_group=id,
-                                                               user_group=obj_id)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('An error occurred during revoking of permission'),
-                    category='error')
-            raise HTTPInternalServerError()
-
-    def show(self, id, format='html'):
-        """GET /users_groups/id: Show a specific item"""
-        # url('users_group', id=ID)
-
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
-    def edit(self, id, format='html'):
-        """GET /users_groups/id/edit: Form to edit an existing item"""
-        # url('edit_users_group', id=ID)
-
-        c.users_group = UserGroup.get_or_404(id)
-        self.__load_data(id)
-
-        defaults = self.__load_defaults(id)
-
-        return htmlfill.render(
-            render('admin/users_groups/users_group_edit.html'),
-            defaults=defaults,
-            encoding="UTF-8",
-            force_defaults=False
-        )
-
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
-    def update_perm(self, id):
-        """PUT /users_perm/id: Update an existing item"""
-        # url('users_group_perm', id=ID, method='put')
-
-        users_group = UserGroup.get_or_404(id)
-
-        try:
-            form = CustomDefaultPermissionsForm()()
-            form_result = form.to_python(request.POST)
-
-            inherit_perms = form_result['inherit_default_permissions']
-            users_group.inherit_default_permissions = inherit_perms
-            Session().add(users_group)
-            usergroup_model = UserGroupModel()
-
-            defs = UserGroupToPerm.query()\
-                .filter(UserGroupToPerm.users_group == users_group)\
-                .all()
-            for ug in defs:
-                Session().delete(ug)
-
-            if form_result['create_repo_perm']:
-                usergroup_model.grant_perm(id, 'hg.create.repository')
-            else:
-                usergroup_model.grant_perm(id, 'hg.create.none')
-            if form_result['create_user_group_perm']:
-                usergroup_model.grant_perm(id, 'hg.usergroup.create.true')
-            else:
-                usergroup_model.grant_perm(id, 'hg.usergroup.create.false')
-            if form_result['fork_repo_perm']:
-                usergroup_model.grant_perm(id, 'hg.fork.repository')
-            else:
-                usergroup_model.grant_perm(id, 'hg.fork.none')
-
-            h.flash(_("Updated permissions"), category='success')
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('An error occurred during permissions saving'),
-                    category='error')
-
-        return redirect(url('edit_users_group', id=id))
--- a/rhodecode/controllers/api/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/api/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,19 +1,8 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.api
-    ~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    JSON RPC controller
-
-    :created_on: Aug 20, 2011
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; version 2
-# of the License or (at your opinion) any later version of the license.
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -21,9 +10,18 @@
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
-# MA  02110-1301, USA.
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.api
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+JSON RPC controller
+
+:created_on: Aug 20, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import inspect
 import logging
@@ -35,15 +33,14 @@
 from paste.response import replace_header
 from pylons.controllers import WSGIController
 
-from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
-HTTPBadRequest, HTTPError
+from webob.exc import HTTPError
 
 from rhodecode.model.db import User
 from rhodecode.model import meta
 from rhodecode.lib.compat import izip_longest, json
 from rhodecode.lib.auth import AuthUser
-from rhodecode.lib.base import _get_ip_addr, _get_access_path
-from rhodecode.lib.utils2 import safe_unicode
+from rhodecode.lib.base import _get_ip_addr as _get_ip, _get_access_path
+from rhodecode.lib.utils2 import safe_unicode, safe_str
 
 log = logging.getLogger('JSONRPC')
 
@@ -55,18 +52,22 @@
         super(JSONRPCError, self).__init__()
 
     def __str__(self):
-        return str(self.message)
+        return safe_str(self.message)
 
 
 def jsonrpc_error(message, retid=None, code=None):
     """
     Generate a Response object with a JSON-RPC error body
+
+    :param code:
+    :param retid:
+    :param message:
     """
     from pylons.controllers.util import Response
     return Response(
-            body=json.dumps(dict(id=retid, result=None, error=message)),
-            status=code,
-            content_type='application/json'
+        body=json.dumps(dict(id=retid, result=None, error=message)),
+        status=code,
+        content_type='application/json'
     )
 
 
@@ -85,7 +86,7 @@
      """
 
     def _get_ip_addr(self, environ):
-        return _get_ip_addr(environ)
+        return _get_ip(environ)
 
     def _get_method_args(self):
         """
@@ -99,6 +100,12 @@
         Parse the request body as JSON, look up the method on the
         controller and if it exists, dispatch to it.
         """
+        try:
+            return self._handle_request(environ, start_response)
+        finally:
+            meta.Session.remove()
+
+    def _handle_request(self, environ, start_response):
         start = time.time()
         ip_addr = self.ip_addr = self._get_ip_addr(environ)
         self._req_id = None
@@ -119,12 +126,12 @@
         raw_body = environ['wsgi.input'].read(length)
 
         try:
-            json_body = json.loads(urllib.unquote_plus(raw_body))
+            json_body = json.loads(raw_body)
         except ValueError, e:
             # catch JSON errors Here
             return jsonrpc_error(retid=self._req_id,
-                                 message="JSON parse error ERR:%s RAW:%r" \
-                                 % (e, urllib.unquote_plus(raw_body)))
+                                 message="JSON parse error ERR:%s RAW:%r"
+                                 % (e, raw_body))
 
         # check AUTH based on API KEY
         try:
@@ -154,9 +161,9 @@
             auth_u = AuthUser(u.user_id, self._req_api_key, ip_addr=ip_addr)
             if not auth_u.ip_allowed:
                 return jsonrpc_error(retid=self._req_id,
-                        message='request from IP:%s not allowed' % (ip_addr))
+                        message='request from IP:%s not allowed' % (ip_addr,))
             else:
-                log.info('Access for IP:%s allowed' % (ip_addr))
+                log.info('Access for IP:%s allowed' % (ip_addr,))
 
         except Exception, e:
             return jsonrpc_error(retid=self._req_id,
@@ -206,7 +213,7 @@
 
             # skip the required param check if it's default value is
             # NotImplementedType (default_empty)
-            if (default == default_empty and arg not in self._request_params):
+            if default == default_empty and arg not in self._request_params:
                 return jsonrpc_error(
                     retid=self._req_id,
                     message=(
@@ -237,7 +244,7 @@
         replace_header(headers, 'Content-Type', 'application/json')
         start_response(status[0], headers, exc_info[0])
         log.info('IP: %s Request to %s time: %.3fs' % (
-            _get_ip_addr(environ),
+            self._get_ip_addr(environ),
             safe_unicode(_get_access_path(environ)), time.time() - start)
         )
         return output
@@ -246,24 +253,23 @@
         """
         Implement dispatch interface specified by WSGIController
         """
+        raw_response = ''
         try:
             raw_response = self._inspect_call(self._func)
             if isinstance(raw_response, HTTPError):
                 self._error = str(raw_response)
         except JSONRPCError, e:
-            self._error = str(e)
+            self._error = safe_str(e)
         except Exception, e:
-            log.error('Encountered unhandled exception: %s' \
-                      % traceback.format_exc())
+            log.error('Encountered unhandled exception: %s'
+                      % (traceback.format_exc(),))
             json_exc = JSONRPCError('Internal server error')
-            self._error = str(json_exc)
+            self._error = safe_str(json_exc)
 
         if self._error is not None:
             raw_response = None
 
-        response = dict(id=self._req_id, result=raw_response,
-                        error=self._error)
-
+        response = dict(id=self._req_id, result=raw_response, error=self._error)
         try:
             return json.dumps(response)
         except TypeError, e:
@@ -280,7 +286,7 @@
         """
         Return method named by `self._req_method` in controller if able
         """
-        log.debug('Trying to find JSON-RPC method: %s' % self._req_method)
+        log.debug('Trying to find JSON-RPC method: %s' % (self._req_method,))
         if self._req_method.startswith('_'):
             raise AttributeError("Method not allowed")
 
@@ -293,4 +299,4 @@
         if isinstance(func, types.MethodType):
             return func
         else:
-            raise AttributeError("No such method: %s" % self._req_method)
+            raise AttributeError("No such method: %s" % (self._req_method,))
--- a/rhodecode/controllers/api/api.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/api/api.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,19 +1,8 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.api
-    ~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    API controller for RhodeCode
-
-    :created_on: Aug 20, 2011
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; version 2
-# of the License or (at your opinion) any later version of the license.
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -21,31 +10,46 @@
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
-# MA  02110-1301, USA.
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.api
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+API controller for RhodeCode
+
+:created_on: Aug 20, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import time
 import traceback
 import logging
+from sqlalchemy import or_
 
 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
-from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
-    HasPermissionAllDecorator, HasPermissionAnyDecorator, \
-    HasPermissionAnyApi, HasRepoPermissionAnyApi
+from rhodecode.lib.auth import (
+    PasswordGenerator, AuthUser, HasPermissionAllDecorator,
+    HasPermissionAnyDecorator, HasPermissionAnyApi, HasRepoPermissionAnyApi,
+    HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAny)
 from rhodecode.lib.utils import map_groups, repo2db_mapper
-from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_int
+from rhodecode.lib.utils2 import (
+    str2bool, time_to_datetime, safe_int, Optional, OAttr)
 from rhodecode.model.meta import Session
-from rhodecode.model.scm import ScmModel
+from rhodecode.model.repo_group import RepoGroupModel
+from rhodecode.model.scm import ScmModel, UserGroupList
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.user import UserModel
-from rhodecode.model.users_group import UserGroupModel
-from rhodecode.model.repos_group import ReposGroupModel
-from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap,\
-    Permission, User, Gist
+from rhodecode.model.user_group import UserGroupModel
+from rhodecode.model.gist import GistModel
+from rhodecode.model.db import (
+    Repository, RhodeCodeSetting, UserIpMap, Permission, User, Gist,
+    RepoGroup)
 from rhodecode.lib.compat import json
-from rhodecode.lib.exceptions import DefaultUserException
-from rhodecode.model.gist import GistModel
+from rhodecode.lib.exceptions import (
+    DefaultUserException, UserGroupsAssignedException)
 
 log = logging.getLogger(__name__)
 
@@ -59,56 +63,6 @@
         updates[name] = attr
 
 
-class OptionalAttr(object):
-    """
-    Special Optional Option that defines other attribute
-    """
-    def __init__(self, attr_name):
-        self.attr_name = attr_name
-
-    def __repr__(self):
-        return '<OptionalAttr:%s>' % self.attr_name
-
-    def __call__(self):
-        return self
-#alias
-OAttr = OptionalAttr
-
-
-class Optional(object):
-    """
-    Defines an optional parameter::
-
-        param = param.getval() if isinstance(param, Optional) else param
-        param = param() if isinstance(param, Optional) else param
-
-    is equivalent of::
-
-        param = Optional.extract(param)
-
-    """
-    def __init__(self, type_):
-        self.type_ = type_
-
-    def __repr__(self):
-        return '<Optional:%s>' % self.type_.__repr__()
-
-    def __call__(self):
-        return self.getval()
-
-    def getval(self):
-        """
-        returns value from this Optional instance
-        """
-        return self.type_
-
-    @classmethod
-    def extract(cls, val):
-        if isinstance(val, cls):
-            return val.getval()
-        return val
-
-
 def get_user_or_error(userid):
     """
     Get user by id or name or return JsonRPCError if not found
@@ -117,7 +71,7 @@
     """
     user = UserModel().get_user(userid)
     if user is None:
-        raise JSONRPCError("user `%s` does not exist" % userid)
+        raise JSONRPCError("user `%s` does not exist" % (userid,))
     return user
 
 
@@ -129,7 +83,7 @@
     """
     repo = RepoModel().get_repo(repoid)
     if repo is None:
-        raise JSONRPCError('repository `%s` does not exist' % (repoid))
+        raise JSONRPCError('repository `%s` does not exist' % (repoid,))
     return repo
 
 
@@ -139,60 +93,103 @@
 
     :param repogroupid:
     """
-    repo_group = ReposGroupModel()._get_repo_group(repogroupid)
+    repo_group = RepoGroupModel()._get_repo_group(repogroupid)
     if repo_group is None:
         raise JSONRPCError(
             'repository group `%s` does not exist' % (repogroupid,))
     return repo_group
 
 
-def get_users_group_or_error(usersgroupid):
+def get_user_group_or_error(usergroupid):
     """
     Get user group by id or name or return JsonRPCError if not found
 
-    :param userid:
+    :param usergroupid:
     """
-    users_group = UserGroupModel().get_group(usersgroupid)
-    if users_group is None:
-        raise JSONRPCError('user group `%s` does not exist' % usersgroupid)
-    return users_group
+    user_group = UserGroupModel().get_group(usergroupid)
+    if user_group is None:
+        raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
+    return user_group
 
 
-def get_perm_or_error(permid):
+def get_perm_or_error(permid, prefix=None):
     """
     Get permission by id or name or return JsonRPCError if not found
 
-    :param userid:
+    :param permid:
     """
     perm = Permission.get_by_key(permid)
     if perm is None:
-        raise JSONRPCError('permission `%s` does not exist' % (permid))
+        raise JSONRPCError('permission `%s` does not exist' % (permid,))
+    if prefix:
+        if not perm.permission_name.startswith(prefix):
+            raise JSONRPCError('permission `%s` is invalid, '
+                               'should start with %s' % (permid, prefix))
     return perm
 
 
+def get_gist_or_error(gistid):
+    """
+    Get gist by id or gist_access_id or return JsonRPCError if not found
+
+    :param gistid:
+    """
+    gist = GistModel().get_gist(gistid)
+    if gist is None:
+        raise JSONRPCError('gist `%s` does not exist' % (gistid,))
+    return gist
+
+
 class ApiController(JSONRPCController):
     """
     API Controller
 
+    Each method takes USER as first argument. This is then, based on given
+    API_KEY propagated as instance of user object who's making the call.
 
-    Each method needs to have USER as argument this is then based on given
-    API_KEY propagated as instance of user object
+    example function::
 
-    Preferably this should be first argument also
-
+        def func(apiuser,arg1, arg2,...):
+            pass
 
     Each function should also **raise** JSONRPCError for any
-    errors that happens
+    errors that happens.
 
     """
 
     @HasPermissionAllDecorator('hg.admin')
+    def test(self, apiuser, args):
+        return args
+
+    @HasPermissionAllDecorator('hg.admin')
     def pull(self, apiuser, repoid):
         """
-        Dispatch pull action on given repo
+        Triggers a pull from remote location on given repo. Can be used to
+        automatically keep remote repos up to date. This command can be executed
+        only using api_key belonging to user with admin rights
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repoid: repository name or repository id
+        :type repoid: str or int
+
+        OUTPUT::
 
-        :param apiuser:
-        :param repoid:
+          id : <id_given_in_input>
+          result : {
+            "msg": "Pulled from `<repository name>`"
+            "repository": "<repository name>"
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "Unable to pull changes from `<reponame>`"
+          }
+
         """
 
         repo = get_repo_or_error(repoid)
@@ -200,7 +197,10 @@
         try:
             ScmModel().pull_changes(repo.repo_name,
                                     self.rhodecode_user.username)
-            return 'Pulled from `%s`' % repo.repo_name
+            return dict(
+                msg='Pulled from `%s`' % repo.repo_name,
+                repository=repo.repo_name
+            )
         except Exception:
             log.error(traceback.format_exc())
             raise JSONRPCError(
@@ -210,12 +210,34 @@
     @HasPermissionAllDecorator('hg.admin')
     def rescan_repos(self, apiuser, remove_obsolete=Optional(False)):
         """
-        Dispatch rescan repositories action. If remove_obsolete is set
+        Triggers rescan repositories action. If remove_obsolete is set
         than also delete repos that are in database but not in the filesystem.
-        aka "clean zombies"
+        aka "clean zombies". This command can be executed only using api_key
+        belonging to user with admin rights.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param remove_obsolete: deletes repositories from
+            database that are not found on the filesystem
+        :type remove_obsolete: Optional(bool)
+
+        OUTPUT::
 
-        :param apiuser:
-        :param remove_obsolete:
+          id : <id_given_in_input>
+          result : {
+            'added': [<added repository name>,...]
+            'removed': [<removed repository name>,...]
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            'Error occurred during rescan repositories action'
+          }
+
         """
 
         try:
@@ -231,22 +253,47 @@
 
     def invalidate_cache(self, apiuser, repoid):
         """
-        Dispatch cache invalidation action on given repo
+        Invalidate cache for repository.
+        This command can be executed only using api_key belonging to user with admin
+        rights or regular user that have write or admin or write access to repository.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repoid: repository name or repository id
+        :type repoid: str or int
+
+        OUTPUT::
 
-        :param apiuser:
-        :param repoid:
+          id : <id_given_in_input>
+          result : {
+            'msg': Cache for repository `<repository name>` was invalidated,
+            'repository': <repository name>
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            'Error occurred during cache invalidation action'
+          }
+
         """
         repo = get_repo_or_error(repoid)
         if not HasPermissionAnyApi('hg.admin')(user=apiuser):
             # check if we have admin permission for this repo !
-            if HasRepoPermissionAnyApi('repository.admin',
-                                       'repository.write')(user=apiuser,
-                                            repo_name=repo.repo_name) is False:
-                raise JSONRPCError('repository `%s` does not exist' % (repoid))
+            if not HasRepoPermissionAnyApi('repository.admin',
+                                           'repository.write')(
+                    user=apiuser, repo_name=repo.repo_name):
+                raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         try:
             ScmModel().mark_for_invalidation(repo.repo_name)
-            return ('Caches of repository `%s` was invalidated' % repoid)
+            return dict(
+                msg='Cache for repository `%s` was invalidated' % (repoid,),
+                repository=repo.repo_name
+            )
         except Exception:
             log.error(traceback.format_exc())
             raise JSONRPCError(
@@ -257,14 +304,47 @@
     def lock(self, apiuser, repoid, locked=Optional(None),
              userid=Optional(OAttr('apiuser'))):
         """
-        Set locking state on particular repository by given user, if
-        this command is runned by non-admin account userid is set to user
-        who is calling this method
+        Set locking state on given repository by given user. If userid param
+        is skipped, then it is set to id of user whos calling this method.
+        If locked param is skipped then function shows current lock state of
+        given repo. This command can be executed only using api_key belonging
+        to user with admin rights or regular user that have admin or write
+        access to repository.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repoid: repository name or repository id
+        :type repoid: str or int
+        :param locked: lock state to be set
+        :type locked: Optional(bool)
+        :param userid: set lock as user
+        :type userid: Optional(str or int)
+
+        OUTPUT::
 
-        :param apiuser:
-        :param repoid:
-        :param userid:
-        :param locked:
+          id : <id_given_in_input>
+          result : {
+            'repo': '<reponame>',
+            'locked': <bool: lock state>,
+            'locked_since': <int: lock timestamp>,
+            'locked_by': <username of person who made the lock>,
+            'lock_state_changed': <bool: True if lock state has been changed in this request>,
+            'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
+            or
+            'msg': 'Repo `<repository name>` not locked.'
+            or
+            'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            'Error occurred locking repository `<reponame>`
+          }
+
         """
         repo = get_repo_or_error(repoid)
         if HasPermissionAnyApi('hg.admin')(user=apiuser):
@@ -272,14 +352,14 @@
         elif HasRepoPermissionAnyApi('repository.admin',
                                      'repository.write')(user=apiuser,
                                                          repo_name=repo.repo_name):
-            #make sure normal user does not pass someone else userid,
-            #he is not allowed to do that
+            # make sure normal user does not pass someone else userid,
+            # he is not allowed to do that
             if not isinstance(userid, Optional) and userid != apiuser.user_id:
                 raise JSONRPCError(
                     'userid is not the same as your user'
                 )
         else:
-            raise JSONRPCError('repository `%s` does not exist' % (repoid))
+            raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         if isinstance(userid, Optional):
             userid = apiuser.user_id
@@ -295,6 +375,7 @@
                     'locked': False,
                     'locked_since': None,
                     'locked_by': None,
+                    'lock_state_changed': False,
                     'msg': 'Repo `%s` not locked.' % repo.repo_name
                 }
                 return _d
@@ -306,8 +387,9 @@
                     'locked': True,
                     'locked_since': time_,
                     'locked_by': lock_user.username,
-                    'msg': ('Repo `%s` locked by `%s`. '
-                            % (repo.repo_name,
+                    'lock_state_changed': False,
+                    'msg': ('Repo `%s` locked by `%s` on `%s`.'
+                            % (repo.repo_name, lock_user.username,
                                json.dumps(time_to_datetime(time_))))
                 }
                 return _d
@@ -327,6 +409,7 @@
                     'locked': locked,
                     'locked_since': lock_time,
                     'locked_by': user.username,
+                    'lock_state_changed': True,
                     'msg': ('User `%s` set lock state for repo `%s` to `%s`'
                             % (user.username, repo.repo_name, locked))
                 }
@@ -339,28 +422,39 @@
 
     def get_locks(self, apiuser, userid=Optional(OAttr('apiuser'))):
         """
-        Get all locks for given userid, if
+        Get all repositories with locks for given userid, if
         this command is runned by non-admin account userid is set to user
-        who is calling this method, thus returning locks for himself
+        who is calling this method, thus returning locks for himself.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param userid: User to get locks for
+        :type userid: Optional(str or int)
 
-        :param apiuser:
-        :param userid:
+        OUTPUT::
+
+          id : <id_given_in_input>
+          result : {
+            [repo_object, repo_object,...]
+          }
+          error :  null
         """
 
         if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            #make sure normal user does not pass someone else userid,
-            #he is not allowed to do that
+            # make sure normal user does not pass someone else userid,
+            # he is not allowed to do that
             if not isinstance(userid, Optional) and userid != apiuser.user_id:
                 raise JSONRPCError(
                     'userid is not the same as your user'
                 )
+
         ret = []
         if isinstance(userid, Optional):
             user = None
         else:
             user = get_user_or_error(userid)
 
-        #show all locks
+        # show all locks
         for r in Repository.getAll():
             userid, time_ = r.locked
             if time_:
@@ -375,31 +469,114 @@
         return ret
 
     @HasPermissionAllDecorator('hg.admin')
-    def show_ip(self, apiuser, userid):
+    def get_ip(self, apiuser, userid=Optional(OAttr('apiuser'))):
         """
         Shows IP address as seen from RhodeCode server, together with all
-        defined IP addresses for given user
+        defined IP addresses for given user. If userid is not passed data is
+        returned for user who's calling this function.
+        This command can be executed only using api_key belonging to user with
+        admin rights.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param userid: username to show ips for
+        :type userid: Optional(str or int)
+
+        OUTPUT::
 
-        :param apiuser:
-        :param userid:
+            id : <id_given_in_input>
+            result : {
+                         "server_ip_addr": "<ip_from_clien>",
+                         "user_ips": [
+                                        {
+                                           "ip_addr": "<ip_with_mask>",
+                                           "ip_range": ["<start_ip>", "<end_ip>"],
+                                        },
+                                        ...
+                                     ]
+            }
+
         """
+        if isinstance(userid, Optional):
+            userid = apiuser.user_id
         user = get_user_or_error(userid)
         ips = UserIpMap.query().filter(UserIpMap.user == user).all()
         return dict(
-            ip_addr_server=self.ip_addr,
+            server_ip_addr=self.ip_addr,
             user_ips=ips
         )
 
+    # alias for old
+    show_ip = get_ip
+
+    @HasPermissionAllDecorator('hg.admin')
+    def get_server_info(self, apiuser):
+        """
+        return server info, including RhodeCode version and installed packages
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+
+        OUTPUT::
+
+          id : <id_given_in_input>
+          result : {
+            'modules': [<module name>,...]
+            'py_version': <python version>,
+            'platform': <platform type>,
+            'rhodecode_version': <rhodecode version>
+          }
+          error :  null
+        """
+        return RhodeCodeSetting.get_server_info()
+
     def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))):
-        """"
-        Get a user by username, or userid, if userid is given
+        """
+        Get's an user by username or user_id, Returns empty result if user is
+        not found. If userid param is skipped it is set to id of user who is
+        calling this method. This command can be executed only using api_key
+        belonging to user with admin rights, or regular users that cannot
+        specify different userid than theirs
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param userid: user to get data for
+        :type userid: Optional(str or int)
+
+        OUTPUT::
 
-        :param apiuser:
-        :param userid:
+            id : <id_given_in_input>
+            result: None if user does not exist or
+                    {
+                        "user_id" :     "<user_id>",
+                        "api_key" :     "<api_key>",
+                        "api_keys":     "[<list of all api keys including additional ones>]"
+                        "username" :    "<username>",
+                        "firstname":    "<firstname>",
+                        "lastname" :    "<lastname>",
+                        "email" :       "<email>",
+                        "emails":       "[<list of all emails including additional ones>]",
+                        "ip_addresses": "[<ip_addresse_for_user>,...]",
+                        "active" :      "<bool: user active>",
+                        "admin" :ย       "<bool: user is admin>",
+                        "extern_name" : "<extern_name>",
+                        "extern_type" : "<extern type>
+                        "last_login":   "<last_login>",
+                        "permissions": {
+                            "global": ["hg.create.repository",
+                                       "repository.read",
+                                       "hg.register.manual_activate"],
+                            "repositories": {"repo1": "repository.none"},
+                            "repositories_groups": {"Group1": "group.read"}
+                         },
+                    }
+
+            error:  null
+
         """
         if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            #make sure normal user does not pass someone else userid,
-            #he is not allowed to do that
+            # make sure normal user does not pass someone else userid,
+            # he is not allowed to do that
             if not isinstance(userid, Optional) and userid != apiuser.user_id:
                 raise JSONRPCError(
                     'userid is not the same as your user'
@@ -415,47 +592,91 @@
 
     @HasPermissionAllDecorator('hg.admin')
     def get_users(self, apiuser):
-        """"
-        Get all users
+        """
+        Lists all existing users. This command can be executed only using api_key
+        belonging to user with admin rights.
 
-        :param apiuser:
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: [<user_object>, ...]
+            error:  null
         """
 
         result = []
-        users_list = User.query().order_by(User.username)\
-                        .filter(User.username != User.DEFAULT_USER)\
-                        .all()
+        users_list = User.query().order_by(User.username) \
+            .filter(User.username != User.DEFAULT_USER) \
+            .all()
         for user in users_list:
             result.append(user.get_api_data())
         return result
 
     @HasPermissionAllDecorator('hg.admin')
-    def create_user(self, apiuser, username, email, password=Optional(None),
-                    firstname=Optional(None), lastname=Optional(None),
+    def create_user(self, apiuser, username, email, password=Optional(''),
+                    firstname=Optional(''), lastname=Optional(''),
                     active=Optional(True), admin=Optional(False),
-                    ldap_dn=Optional(None)):
+                    extern_name=Optional('rhodecode'),
+                    extern_type=Optional('rhodecode')):
         """
-        Create new user
+        Creates new user. Returns new user object. This command can
+        be executed only using api_key belonging to user with admin rights.
 
-        :param apiuser:
-        :param username:
-        :param email:
-        :param password:
-        :param firstname:
-        :param lastname:
-        :param active:
-        :param admin:
-        :param ldap_dn:
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param username: new username
+        :type username: str or int
+        :param email: email
+        :type email: str
+        :param password: password
+        :type password: Optional(str)
+        :param firstname: firstname
+        :type firstname: Optional(str)
+        :param lastname: lastname
+        :type lastname: Optional(str)
+        :param active: active
+        :type active: Optional(bool)
+        :param admin: admin
+        :type admin: Optional(bool)
+        :param extern_name: name of extern
+        :type extern_name: Optional(str)
+        :param extern_type: extern_type
+        :type extern_type: Optional(str)
+
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "msg" : "created new user `<username>`",
+                      "user": <user_obj>
+                    }
+            error:  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "user `<username>` already exist"
+            or
+            "email `<email>` already exist"
+            or
+            "failed to create user `<username>`"
+          }
+
         """
 
         if UserModel().get_by_username(username):
-            raise JSONRPCError("user `%s` already exist" % username)
+            raise JSONRPCError("user `%s` already exist" % (username,))
 
         if UserModel().get_by_email(email, case_insensitive=True):
-            raise JSONRPCError("email `%s` already exist" % email)
+            raise JSONRPCError("email `%s` already exist" % (email,))
 
-        if Optional.extract(ldap_dn):
-            # generate temporary password if ldap_dn
+        if Optional.extract(extern_name):
+            # generate temporary password if user is external
             password = PasswordGenerator().gen_password(length=8)
 
         try:
@@ -467,7 +688,8 @@
                 lastname=Optional.extract(lastname),
                 active=Optional.extract(active),
                 admin=Optional.extract(admin),
-                ldap_dn=Optional.extract(ldap_dn)
+                extern_type=Optional.extract(extern_type),
+                extern_name=Optional.extract(extern_name)
             )
             Session().commit()
             return dict(
@@ -476,48 +698,77 @@
             )
         except Exception:
             log.error(traceback.format_exc())
-            raise JSONRPCError('failed to create user `%s`' % username)
+            raise JSONRPCError('failed to create user `%s`' % (username,))
 
     @HasPermissionAllDecorator('hg.admin')
     def update_user(self, apiuser, userid, username=Optional(None),
-                    email=Optional(None), firstname=Optional(None),
-                    lastname=Optional(None), active=Optional(None),
-                    admin=Optional(None), ldap_dn=Optional(None),
-                    password=Optional(None)):
+                    email=Optional(None),password=Optional(None),
+                    firstname=Optional(None), lastname=Optional(None),
+                    active=Optional(None), admin=Optional(None),
+                    extern_type=Optional(None), extern_name=Optional(None),):
         """
-        Updates given user
+        updates given user if such user exists. This command can
+        be executed only using api_key belonging to user with admin rights.
 
-        :param apiuser:
-        :param userid:
-        :param username:
-        :param email:
-        :param firstname:
-        :param lastname:
-        :param active:
-        :param admin:
-        :param ldap_dn:
-        :param password:
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param userid: userid to update
+        :type userid: str or int
+        :param username: new username
+        :type username: str or int
+        :param email: email
+        :type email: str
+        :param password: password
+        :type password: Optional(str)
+        :param firstname: firstname
+        :type firstname: Optional(str)
+        :param lastname: lastname
+        :type lastname: Optional(str)
+        :param active: active
+        :type active: Optional(bool)
+        :param admin: admin
+        :type admin: Optional(bool)
+        :param extern_name:
+        :type extern_name: Optional(str)
+        :param extern_type:
+        :type extern_type: Optional(str)
+
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "msg" : "updated user ID:<userid> <username>",
+                      "user": <user_object>,
+                    }
+            error:  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to update user `<username>`"
+          }
+
         """
 
         user = get_user_or_error(userid)
 
-        # call function and store only updated arguments
+        # only non optional arguments will be stored in updates
         updates = {}
 
-        def store_update(attr, name):
-            if not isinstance(attr, Optional):
-                updates[name] = attr
-
         try:
 
-            store_update(username, 'username')
-            store_update(password, 'password')
-            store_update(email, 'email')
-            store_update(firstname, 'name')
-            store_update(lastname, 'lastname')
-            store_update(active, 'active')
-            store_update(admin, 'admin')
-            store_update(ldap_dn, 'ldap_dn')
+            store_update(updates, username, 'username')
+            store_update(updates, password, 'password')
+            store_update(updates, email, 'email')
+            store_update(updates, firstname, 'name')
+            store_update(updates, lastname, 'lastname')
+            store_update(updates, active, 'active')
+            store_update(updates, admin, 'admin')
+            store_update(updates, extern_name, 'extern_name')
+            store_update(updates, extern_type, 'extern_type')
 
             user = UserModel().update_user(user, **updates)
             Session().commit()
@@ -530,15 +781,36 @@
             raise JSONRPCError('editing default user is forbidden')
         except Exception:
             log.error(traceback.format_exc())
-            raise JSONRPCError('failed to update user `%s`' % userid)
+            raise JSONRPCError('failed to update user `%s`' % (userid,))
 
     @HasPermissionAllDecorator('hg.admin')
     def delete_user(self, apiuser, userid):
-        """"
-        Deletes an user
+        """
+        deletes givenuser if such user exists. This command can
+        be executed only using api_key belonging to user with admin rights.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param userid: user to delete
+        :type userid: str or int
+
+        OUTPUT::
 
-        :param apiuser:
-        :param userid:
+            id : <id_given_in_input>
+            result: {
+                      "msg" : "deleted user ID:<userid> <username>",
+                      "user": null
+                    }
+            error:  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to delete user ID:<userid> <username>"
+          }
+
         """
         user = get_user_or_error(userid)
 
@@ -550,57 +822,114 @@
                 user=None
             )
         except Exception:
+
             log.error(traceback.format_exc())
-            raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id,
-                                                              user.username))
+            raise JSONRPCError('failed to delete user ID:%s %s'
+                               % (user.user_id, user.username))
 
-    @HasPermissionAllDecorator('hg.admin')
-    def get_users_group(self, apiuser, usersgroupid):
-        """"
-        Get user group by name or id
+    # permission check inside
+    def get_user_group(self, apiuser, usergroupid):
+        """
+        Gets an existing user group. This command can be executed only using api_key
+        belonging to user with admin rights or user who has at least
+        read access to user group.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param usergroupid: id of user_group to edit
+        :type usergroupid: str or int
+
+        OUTPUT::
 
-        :param apiuser:
-        :param usersgroupid:
-        """
-        users_group = get_users_group_or_error(usersgroupid)
+            id : <id_given_in_input>
+            result : None if group not exist
+                     {
+                       "users_group_id" : "<id>",
+                       "group_name" :     "<groupname>",
+                       "active":          "<bool>",
+                       "members" :  [<user_obj>,...]
+                     }
+            error : null
 
-        data = users_group.get_api_data()
+        """
+        user_group = get_user_group_or_error(usergroupid)
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have at least read permission for this user group !
+            _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
+            if not HasUserGroupPermissionAny(*_perms)(
+                    user=apiuser, user_group_name=user_group.users_group_name):
+                raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
-        members = []
-        for user in users_group.members:
-            user = user.user
-            members.append(user.get_api_data())
-        data['members'] = members
+        data = user_group.get_api_data()
         return data
 
-    @HasPermissionAllDecorator('hg.admin')
-    def get_users_groups(self, apiuser):
-        """"
-        Get all user groups
+    # permission check inside
+    def get_user_groups(self, apiuser):
+        """
+        Lists all existing user groups. This command can be executed only using
+        api_key belonging to user with admin rights or user who has at least
+        read access to user group.
 
-        :param apiuser:
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result : [<user_group_obj>,...]
+            error : null
         """
 
         result = []
-        for users_group in UserGroupModel().get_all():
-            result.append(users_group.get_api_data())
+        _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
+        extras = {'user': apiuser}
+        for user_group in UserGroupList(UserGroupModel().get_all(),
+                                        perm_set=_perms, extra_kwargs=extras):
+            result.append(user_group.get_api_data())
         return result
 
-    @HasPermissionAllDecorator('hg.admin')
-    def create_users_group(self, apiuser, group_name,
-                           owner=Optional(OAttr('apiuser')),
-                           active=Optional(True)):
+    @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
+    def create_user_group(self, apiuser, group_name, description=Optional(''),
+                          owner=Optional(OAttr('apiuser')), active=Optional(True)):
         """
-        Creates an new usergroup
+        Creates new user group. This command can be executed only using api_key
+        belonging to user with admin rights or an user who has create user group
+        permission
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param group_name: name of new user group
+        :type group_name: str
+        :param description: group description
+        :type description: str
+        :param owner: owner of group. If not passed apiuser is the owner
+        :type owner: Optional(str or int)
+        :param active: group is active
+        :type active: Optional(bool)
+
+        OUTPUT::
 
-        :param apiuser:
-        :param group_name:
-        :param owner:
-        :param active:
+            id : <id_given_in_input>
+            result: {
+                      "msg": "created new user group `<groupname>`",
+                      "user_group": <user_group_object>
+                    }
+            error:  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "user group `<group name>` already exist"
+            or
+            "failed to create group `<group name>`"
+          }
+
         """
 
         if UserGroupModel().get_by_name(group_name):
-            raise JSONRPCError("user group `%s` already exist" % group_name)
+            raise JSONRPCError("user group `%s` already exist" % (group_name,))
 
         try:
             if isinstance(owner, Optional):
@@ -608,36 +937,191 @@
 
             owner = get_user_or_error(owner)
             active = Optional.extract(active)
-            ug = UserGroupModel().create(name=group_name,
-                                         owner=owner,
-                                         active=active)
+            description = Optional.extract(description)
+            ug = UserGroupModel().create(name=group_name, description=description,
+                                         owner=owner, active=active)
             Session().commit()
             return dict(
                 msg='created new user group `%s`' % group_name,
-                users_group=ug.get_api_data()
+                user_group=ug.get_api_data()
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError('failed to create group `%s`' % (group_name,))
+
+    # permission check inside
+    def update_user_group(self, apiuser, usergroupid, group_name=Optional(''),
+                          description=Optional(''), owner=Optional(None),
+                          active=Optional(True)):
+        """
+        Updates given usergroup.  This command can be executed only using api_key
+        belonging to user with admin rights or an admin of given user group
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param usergroupid: id of user group to update
+        :type usergroupid: str or int
+        :param group_name: name of new user group
+        :type group_name: str
+        :param description: group description
+        :type description: str
+        :param owner: owner of group.
+        :type owner: Optional(str or int)
+        :param active: group is active
+        :type active: Optional(bool)
+
+        OUTPUT::
+
+          id : <id_given_in_input>
+          result : {
+            "msg": 'updated user group ID:<user group id> <user group name>',
+            "user_group": <user_group_object>
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to update user group `<user group name>`"
+          }
+
+        """
+        user_group = get_user_group_or_error(usergroupid)
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have admin permission for this user group !
+            _perms = ('usergroup.admin',)
+            if not HasUserGroupPermissionAny(*_perms)(
+                    user=apiuser, user_group_name=user_group.users_group_name):
+                raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
+
+        if not isinstance(owner, Optional):
+            owner = get_user_or_error(owner)
+
+        updates = {}
+        store_update(updates, group_name, 'users_group_name')
+        store_update(updates, description, 'user_group_description')
+        store_update(updates, owner, 'user')
+        store_update(updates, active, 'users_group_active')
+        try:
+            UserGroupModel().update(user_group, updates)
+            Session().commit()
+            return dict(
+                msg='updated user group ID:%s %s' % (user_group.users_group_id,
+                                                     user_group.users_group_name),
+                user_group=user_group.get_api_data()
             )
         except Exception:
             log.error(traceback.format_exc())
-            raise JSONRPCError('failed to create group `%s`' % group_name)
+            raise JSONRPCError('failed to update user group `%s`' % (usergroupid,))
+
+    # permission check inside
+    def delete_user_group(self, apiuser, usergroupid):
+        """
+        Delete given user group by user group id or name.
+        This command can be executed only using api_key
+        belonging to user with admin rights or an admin of given user group
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param usergroupid:
+        :type usergroupid: int
+
+        OUTPUT::
+
+          id : <id_given_in_input>
+          result : {
+            "msg": "deleted user group ID:<user_group_id> <user_group_name>"
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to delete user group ID:<user_group_id> <user_group_name>"
+            or
+            "RepoGroup assigned to <repo_groups_list>"
+          }
+
+        """
+        user_group = get_user_group_or_error(usergroupid)
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have admin permission for this user group !
+            _perms = ('usergroup.admin',)
+            if not HasUserGroupPermissionAny(*_perms)(
+                    user=apiuser, user_group_name=user_group.users_group_name):
+                raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
-    @HasPermissionAllDecorator('hg.admin')
-    def add_user_to_users_group(self, apiuser, usersgroupid, userid):
-        """"
-        Add a user to a user group
+        try:
+            UserGroupModel().delete(user_group)
+            Session().commit()
+            return dict(
+                msg='deleted user group ID:%s %s' %
+                    (user_group.users_group_id, user_group.users_group_name),
+                user_group=None
+            )
+        except UserGroupsAssignedException, e:
+            log.error(traceback.format_exc())
+            raise JSONRPCError(str(e))
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError('failed to delete user group ID:%s %s' %
+                               (user_group.users_group_id,
+                                user_group.users_group_name)
+            )
+
+    # permission check inside
+    def add_user_to_user_group(self, apiuser, usergroupid, userid):
+        """
+        Adds a user to a user group. If user exists in that group success will be
+        `false`. This command can be executed only using api_key
+        belonging to user with admin rights  or an admin of given user group
 
-        :param apiuser:
-        :param usersgroupid:
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param usergroupid:
+        :type usergroupid: int
         :param userid:
+        :type userid: int
+
+        OUTPUT::
+
+          id : <id_given_in_input>
+          result : {
+              "success": True|False # depends on if member is in group
+              "msg": "added member `<username>` to user group `<groupname>` |
+                      User is already in that group"
+
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to add member to user group `<user_group_name>`"
+          }
+
         """
         user = get_user_or_error(userid)
-        users_group = get_users_group_or_error(usersgroupid)
+        user_group = get_user_group_or_error(usergroupid)
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have admin permission for this user group !
+            _perms = ('usergroup.admin',)
+            if not HasUserGroupPermissionAny(*_perms)(
+                    user=apiuser, user_group_name=user_group.users_group_name):
+                raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
         try:
-            ugm = UserGroupModel().add_user_to_group(users_group, user)
+            ugm = UserGroupModel().add_user_to_group(user_group, user)
             success = True if ugm != True else False
             msg = 'added member `%s` to user group `%s`' % (
-                        user.username, users_group.users_group_name
-                    )
+                user.username, user_group.users_group_name
+            )
             msg = msg if success else 'User is already in that group'
             Session().commit()
 
@@ -649,28 +1133,48 @@
             log.error(traceback.format_exc())
             raise JSONRPCError(
                 'failed to add member to user group `%s`' % (
-                    users_group.users_group_name
+                    user_group.users_group_name,
                 )
             )
 
-    @HasPermissionAllDecorator('hg.admin')
-    def remove_user_from_users_group(self, apiuser, usersgroupid, userid):
+    # permission check inside
+    def remove_user_from_user_group(self, apiuser, usergroupid, userid):
         """
-        Remove user from a group
+        Removes a user from a user group. If user is not in given group success will
+        be `false`. This command can be executed only
+        using api_key belonging to user with admin rights or an admin of given user group
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param usergroupid:
+        :param userid:
+
 
-        :param apiuser:
-        :param usersgroupid:
-        :param userid:
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "success":  True|False,  # depends on if member is in group
+                      "msg": "removed member <username> from user group <groupname> |
+                              User wasn't in group"
+                    }
+            error:  null
+
         """
         user = get_user_or_error(userid)
-        users_group = get_users_group_or_error(usersgroupid)
+        user_group = get_user_group_or_error(usergroupid)
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have admin permission for this user group !
+            _perms = ('usergroup.admin',)
+            if not HasUserGroupPermissionAny(*_perms)(
+                    user=apiuser, user_group_name=user_group.users_group_name):
+                raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
         try:
-            success = UserGroupModel().remove_user_from_group(users_group,
-                                                               user)
+            success = UserGroupModel().remove_user_from_group(user_group, user)
             msg = 'removed member `%s` from user group `%s`' % (
-                        user.username, users_group.users_group_name
-                    )
+                user.username, user_group.users_group_name
+            )
             msg = msg if success else "User wasn't in group"
             Session().commit()
             return dict(success=success, msg=msg)
@@ -678,42 +1182,99 @@
             log.error(traceback.format_exc())
             raise JSONRPCError(
                 'failed to remove member from user group `%s`' % (
-                        users_group.users_group_name
-                    )
+                    user_group.users_group_name,
+                )
             )
 
+    # permission check inside
     def get_repo(self, apiuser, repoid):
-        """"
-        Get repository by name
+        """
+        Gets an existing repository by it's name or repository_id. Members will return
+        either users_group or user associated to that repository. This command can be
+        executed only using api_key belonging to user with admin
+        rights or regular user that have at least read access to repository.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repoid: repository name or repository id
+        :type repoid: str or int
+
+        OUTPUT::
 
-        :param apiuser:
-        :param repoid:
+          id : <id_given_in_input>
+          result : {
+            {
+                "repo_id" :          "<repo_id>",
+                "repo_name" :        "<reponame>"
+                "repo_type" :        "<repo_type>",
+                "clone_uri" :        "<clone_uri>",
+                "enable_downloads":  "<bool>",
+                "enable_locking":    "<bool>",
+                "enable_statistics": "<bool>",
+                "private":           "<bool>",
+                "created_on" :       "<date_time_created>",
+                "description" :      "<description>",
+                "landing_rev":       "<landing_rev>",
+                "last_changeset":    {
+                                       "author":   "<full_author>",
+                                       "date":     "<date_time_of_commit>",
+                                       "message":  "<commit_message>",
+                                       "raw_id":   "<raw_id>",
+                                       "revision": "<numeric_revision>",
+                                       "short_id": "<short_id>"
+                                     }
+                "owner":             "<repo_owner>",
+                "fork_of":           "<name_of_fork_parent>",
+                "members" :     [
+                                  {
+                                    "name":     "<username>",
+                                    "type" :    "user",
+                                    "permission" : "repository.(read|write|admin)"
+                                  },
+                                  โ€ฆ
+                                  {
+                                    "name":     "<usergroup name>",
+                                    "type" :    "user_group",
+                                    "permission" : "usergroup.(read|write|admin)"
+                                  },
+                                  โ€ฆ
+                                ]
+                 "followers":   [<user_obj>, ...]
+                 ]
+            }
+          }
+          error :  null
+
         """
         repo = get_repo_or_error(repoid)
 
         if not HasPermissionAnyApi('hg.admin')(user=apiuser):
             # check if we have admin permission for this repo !
-            if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
-                                            repo_name=repo.repo_name):
-                raise JSONRPCError('repository `%s` does not exist' % (repoid))
+            perms = ('repository.admin', 'repository.write', 'repository.read')
+            if not HasRepoPermissionAnyApi(*perms)(user=apiuser, repo_name=repo.repo_name):
+                raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         members = []
         followers = []
         for user in repo.repo_to_perm:
             perm = user.permission.permission_name
             user = user.user
-            user_data = user.get_api_data()
-            user_data['type'] = "user"
-            user_data['permission'] = perm
+            user_data = {
+                'name': user.username,
+                'type': "user",
+                'permission': perm
+            }
             members.append(user_data)
 
-        for users_group in repo.users_group_to_perm:
-            perm = users_group.permission.permission_name
-            users_group = users_group.users_group
-            users_group_data = users_group.get_api_data()
-            users_group_data['type'] = "users_group"
-            users_group_data['permission'] = perm
-            members.append(users_group_data)
+        for user_group in repo.users_group_to_perm:
+            perm = user_group.permission.permission_name
+            user_group = user_group.users_group
+            user_group_data = {
+                'name': user_group.users_group_name,
+                'type': "user_group",
+                'permission': perm
+            }
+            members.append(user_group_data)
 
         for user in repo.followers:
             followers.append(user.user.get_api_data())
@@ -725,13 +1286,39 @@
 
     # permission check inside
     def get_repos(self, apiuser):
-        """"
-        Get all repositories
+        """
+        Lists all existing repositories. This command can be executed only using
+        api_key belonging to user with admin rights or regular user that have
+        admin, write or read access to repository.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+
+        OUTPUT::
 
-        :param apiuser:
+            id : <id_given_in_input>
+            result: [
+                      {
+                        "repo_id" :          "<repo_id>",
+                        "repo_name" :        "<reponame>"
+                        "repo_type" :        "<repo_type>",
+                        "clone_uri" :        "<clone_uri>",
+                        "private": :         "<bool>",
+                        "created_on" :       "<datetimecreated>",
+                        "description" :      "<description>",
+                        "landing_rev":       "<landing_rev>",
+                        "owner":             "<repo_owner>",
+                        "fork_of":           "<name_of_fork_parent>",
+                        "enable_downloads":  "<bool>",
+                        "enable_locking":    "<bool>",
+                        "enable_statistics": "<bool>",
+                      },
+                      โ€ฆ
+                    ]
+            error:  null
         """
         result = []
-        if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
             repos = RepoModel().get_all_user_repos(user=apiuser)
         else:
             repos = RepoModel().get_all()
@@ -740,21 +1327,49 @@
             result.append(repo.get_api_data())
         return result
 
-    @HasPermissionAllDecorator('hg.admin')
+    # permission check inside
     def get_repo_nodes(self, apiuser, repoid, revision, root_path,
-                       ret_type='all'):
+                       ret_type=Optional('all')):
         """
-        returns a list of nodes and it's children
-        for a given path at given revision. It's possible to specify ret_type
-        to show only files or dirs
+        returns a list of nodes and it's children in a flat list for a given path
+        at given revision. It's possible to specify ret_type to show only `files` or
+        `dirs`.  This command can be executed only using api_key belonging to
+        user with admin rights or regular user that have at least read access to repository.
 
-        :param apiuser:
-        :param repoid: name or id of repository
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repoid: repository name or repository id
+        :type repoid: str or int
         :param revision: revision for which listing should be done
+        :type revision: str
         :param root_path: path from which start displaying
+        :type root_path: str
         :param ret_type: return type 'all|files|dirs' nodes
+        :type ret_type: Optional(str)
+
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: [
+                      {
+                        "name" :        "<name>"
+                        "type" :        "<type>",
+                      },
+                      โ€ฆ
+                    ]
+            error:  null
         """
         repo = get_repo_or_error(repoid)
+
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have admin permission for this repo !
+            perms = ('repository.admin', 'repository.write', 'repository.read')
+            if not HasRepoPermissionAnyApi(*perms)(user=apiuser, repo_name=repo.repo_name):
+                raise JSONRPCError('repository `%s` does not exist' % (repoid,))
+
+        ret_type = Optional.extract(ret_type)
+        _map = {}
         try:
             _d, _f = ScmModel().get_nodes(repo, revision, root_path,
                                           flat=False)
@@ -765,7 +1380,8 @@
             }
             return _map[ret_type]
         except KeyError:
-            raise JSONRPCError('ret_type must be one of %s' % _map.keys())
+            raise JSONRPCError('ret_type must be one of %s'
+                               % (','.join(_map.keys())))
         except Exception:
             log.error(traceback.format_exc())
             raise JSONRPCError(
@@ -774,27 +1390,67 @@
 
     @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
     def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')),
-                    repo_type=Optional('hg'),
-                    description=Optional(''), private=Optional(False),
-                    clone_uri=Optional(None), landing_rev=Optional('tip'),
+                    repo_type=Optional('hg'), description=Optional(''),
+                    private=Optional(False), clone_uri=Optional(None),
+                    landing_rev=Optional('rev:tip'),
                     enable_statistics=Optional(False),
                     enable_locking=Optional(False),
-                    enable_downloads=Optional(False)):
+                    enable_downloads=Optional(False),
+                    copy_permissions=Optional(False)):
         """
-        Create repository, if clone_url is given it makes a remote clone
-        if repo_name is within a group name the groups will be created
-        automatically if they aren't present
+        Creates a repository. If repository name contains "/", all needed repository
+        groups will be created. For example "foo/bar/baz" will create groups
+        "foo", "bar" (with "foo" as parent), and create "baz" repository with
+        "bar" as group. This command can be executed only using api_key
+        belonging to user with admin rights or regular user that have create
+        repository permission. Regular users cannot specify owner parameter
 
-        :param apiuser:
-        :param repo_name:
-        :param onwer:
-        :param repo_type:
-        :param description:
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repo_name: repository name
+        :type repo_name: str
+        :param owner: user_id or username
+        :type owner: Optional(str)
+        :param repo_type: 'hg' or 'git'
+        :type repo_type: Optional(str)
+        :param description: repository description
+        :type description: Optional(str)
         :param private:
+        :type private: bool
         :param clone_uri:
-        :param landing_rev:
+        :type clone_uri: str
+        :param landing_rev: <rev_type>:<rev>
+        :type landing_rev: str
+        :param enable_locking:
+        :type enable_locking: bool
+        :param enable_downloads:
+        :type enable_downloads: bool
+        :param enable_statistics:
+        :type enable_statistics: bool
+        :param copy_permissions: Copy permission from group that repository is
+            beeing created.
+        :type copy_permissions: bool
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "msg": "Created new repository `<reponame>`",
+                      "success": true,
+                      "task": "<celery task id or None if done sync>"
+                    }
+            error:  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+             'failed to create repository `<repo_name>`
+          }
+
         """
-        if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
             if not isinstance(owner, Optional):
                 #forbid setting owner for non-admins
                 raise JSONRPCError(
@@ -823,40 +1479,51 @@
         clone_uri = Optional.extract(clone_uri)
         description = Optional.extract(description)
         landing_rev = Optional.extract(landing_rev)
+        copy_permissions = Optional.extract(copy_permissions)
 
         try:
+            repo_name_cleaned = repo_name.split('/')[-1]
             # create structure of groups and return the last group
-            group = map_groups(repo_name)
-
-            repo = RepoModel().create_repo(
-                repo_name=repo_name,
+            repo_group = map_groups(repo_name)
+            data = dict(
+                repo_name=repo_name_cleaned,
+                repo_name_full=repo_name,
                 repo_type=repo_type,
-                description=description,
+                repo_description=description,
                 owner=owner,
-                private=private,
+                repo_private=private,
                 clone_uri=clone_uri,
-                repos_group=group,
-                landing_rev=landing_rev,
+                repo_group=repo_group,
+                repo_landing_rev=landing_rev,
                 enable_statistics=enable_statistics,
+                enable_locking=enable_locking,
                 enable_downloads=enable_downloads,
-                enable_locking=enable_locking
+                repo_copy_permissions=copy_permissions,
             )
 
-            Session().commit()
+            task = RepoModel().create(form_data=data, cur_user=owner)
+            from celery.result import BaseAsyncResult
+            task_id = None
+            if isinstance(task, BaseAsyncResult):
+                task_id = task.task_id
+            # no commit, it's done in RepoModel, or async via celery
             return dict(
-                msg="Created new repository `%s`" % (repo.repo_name),
-                repo=repo.get_api_data()
+                msg="Created new repository `%s`" % (repo_name,),
+                success=True,  # cannot return the repo data here since fork
+                               # cann be done async
+                task=task_id
             )
         except Exception:
             log.error(traceback.format_exc())
-            raise JSONRPCError('failed to create repository `%s`' % repo_name)
+            raise JSONRPCError(
+                'failed to create repository `%s`' % (repo_name,))
 
     # permission check inside
     def update_repo(self, apiuser, repoid, name=Optional(None),
                     owner=Optional(OAttr('apiuser')),
                     group=Optional(None),
                     description=Optional(''), private=Optional(False),
-                    clone_uri=Optional(None), landing_rev=Optional('tip'),
+                    clone_uri=Optional(None), landing_rev=Optional('rev:tip'),
                     enable_statistics=Optional(False),
                     enable_locking=Optional(False),
                     enable_downloads=Optional(False)):
@@ -917,9 +1584,53 @@
             raise JSONRPCError('failed to update repo `%s`' % repoid)
 
     @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
-    def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')),
+    def fork_repo(self, apiuser, repoid, fork_name,
+                  owner=Optional(OAttr('apiuser')),
                   description=Optional(''), copy_permissions=Optional(False),
-                  private=Optional(False), landing_rev=Optional('tip')):
+                  private=Optional(False), landing_rev=Optional('rev:tip')):
+        """
+        Creates a fork of given repo. In case of using celery this will
+        immidiatelly return success message, while fork is going to be created
+        asynchronous. This command can be executed only using api_key belonging to
+        user with admin rights or regular user that have fork permission, and at least
+        read access to forking repository. Regular users cannot specify owner parameter.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repoid: repository name or repository id
+        :type repoid: str or int
+        :param fork_name:
+        :param owner:
+        :param description:
+        :param copy_permissions:
+        :param private:
+        :param landing_rev:
+
+        INPUT::
+
+            id : <id_for_response>
+            api_key : "<api_key>"
+            args:     {
+                        "repoid" :          "<reponame or repo_id>",
+                        "fork_name":        "<forkname>",
+                        "owner":            "<username or user_id = Optional(=apiuser)>",
+                        "description":      "<description>",
+                        "copy_permissions": "<bool>",
+                        "private":          "<bool>",
+                        "landing_rev":      "<landing_rev>"
+                      }
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "msg": "Created fork of `<reponame>` as `<forkname>`",
+                      "success": true,
+                      "task": "<celery task id or None if done sync>"
+                    }
+            error:  null
+
+        """
         repo = get_repo_or_error(repoid)
         repo_name = repo.repo_name
 
@@ -940,7 +1651,7 @@
                     'Only RhodeCode admin can specify `owner` param'
                 )
         else:
-            raise JSONRPCError('repository `%s` does not exist' % (repoid))
+            raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         if isinstance(owner, Optional):
             owner = apiuser.user_id
@@ -963,12 +1674,18 @@
                 update_after_clone=False,
                 fork_parent_id=repo.repo_id,
             )
-            RepoModel().create_fork(form_data, cur_user=owner)
+            task = RepoModel().create_fork(form_data, cur_user=owner)
+            # no commit, it's done in RepoModel, or async via celery
+            from celery.result import BaseAsyncResult
+            task_id = None
+            if isinstance(task, BaseAsyncResult):
+                task_id = task.task_id
             return dict(
                 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
                                                       fork_name),
-                success=True  # cannot return the repo data here since fork
-                              # cann be done async
+                success=True,  # cannot return the repo data here since fork
+                               # cann be done async
+                task=task_id
             )
         except Exception:
             log.error(traceback.format_exc())
@@ -977,22 +1694,38 @@
                                                             fork_name)
             )
 
-    # perms handled inside
-    def delete_repo(self, apiuser, repoid, forks=Optional(None)):
+    # permission check inside
+    def delete_repo(self, apiuser, repoid, forks=Optional('')):
         """
-        Deletes a given repository
+        Deletes a repository. This command can be executed only using api_key belonging
+        to user with admin rights or regular user that have admin access to repository.
+        When `forks` param is set it's possible to detach or delete forks of deleting
+        repository
 
-        :param apiuser:
-        :param repoid:
-        :param forks: detach or delete, what do do with attached forks for repo
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repoid: repository name or repository id
+        :type repoid: str or int
+        :param forks: `detach` or `delete`, what do do with attached forks for repo
+        :type forks: Optional(str)
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "msg": "Deleted repository `<reponame>`",
+                      "success": true
+                    }
+            error:  null
+
         """
         repo = get_repo_or_error(repoid)
 
-        if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
             # check if we have admin permission for this repo !
-            if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
-                                            repo_name=repo.repo_name) is False:
-                raise JSONRPCError('repository `%s` does not exist' % (repoid))
+            if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
+                                                           repo_name=repo.repo_name):
+                raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         try:
             handle_forks = Optional.extract(forks)
@@ -1004,8 +1737,8 @@
                 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
             elif _forks:
                 raise JSONRPCError(
-                    'Cannot delete `%s` it still contains attached forks'
-                    % repo.repo_name
+                    'Cannot delete `%s` it still contains attached forks' %
+                    (repo.repo_name,)
                 )
 
             RepoModel().delete(repo, forks=forks)
@@ -1017,18 +1750,32 @@
         except Exception:
             log.error(traceback.format_exc())
             raise JSONRPCError(
-                'failed to delete repository `%s`' % repo.repo_name
+                'failed to delete repository `%s`' % (repo.repo_name,)
             )
 
     @HasPermissionAllDecorator('hg.admin')
     def grant_user_permission(self, apiuser, repoid, userid, perm):
         """
         Grant permission for user on given repository, or update existing one
-        if found
+        if found. This command can be executed only using api_key belonging to user
+        with admin rights.
 
-        :param repoid:
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repoid: repository name or repository id
+        :type repoid: str or int
         :param userid:
-        :param perm:
+        :param perm: (repository.(none|read|write|admin))
+        :type perm: str
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
+                      "success": true
+                    }
+            error:  null
         """
         repo = get_repo_or_error(repoid)
         user = get_user_or_error(userid)
@@ -1056,19 +1803,30 @@
     @HasPermissionAllDecorator('hg.admin')
     def revoke_user_permission(self, apiuser, repoid, userid):
         """
-        Revoke permission for user on given repository
+        Revoke permission for user on given repository. This command can be executed
+        only using api_key belonging to user with admin rights.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repoid: repository name or repository id
+        :type repoid: str or int
+        :param userid:
 
-        :param apiuser:
-        :param repoid:
-        :param userid:
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
+                      "success": true
+                    }
+            error:  null
+
         """
 
         repo = get_repo_or_error(repoid)
         user = get_user_or_error(userid)
         try:
-
             RepoModel().revoke_user_permission(repo=repo, user=user)
-
             Session().commit()
             return dict(
                 msg='Revoked perm for user: `%s` in repo: `%s`' % (
@@ -1084,33 +1842,123 @@
                 )
             )
 
-    @HasPermissionAllDecorator('hg.admin')
-    def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
-                                     perm):
+    # permission check inside
+    def grant_user_group_permission(self, apiuser, repoid, usergroupid, perm):
         """
         Grant permission for user group on given repository, or update
-        existing one if found
+        existing one if found. This command can be executed only using
+        api_key belonging to user with admin rights.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repoid: repository name or repository id
+        :type repoid: str or int
+        :param usergroupid: id of usergroup
+        :type usergroupid: str or int
+        :param perm: (repository.(none|read|write|admin))
+        :type perm: str
+
+        OUTPUT::
 
-        :param apiuser:
-        :param repoid:
-        :param usersgroupid:
-        :param perm:
+          id : <id_given_in_input>
+          result : {
+            "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
+            "success": true
+
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
+          }
+
         """
         repo = get_repo_or_error(repoid)
         perm = get_perm_or_error(perm)
-        users_group = get_users_group_or_error(usersgroupid)
+        user_group = get_user_group_or_error(usergroupid)
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have admin permission for this repo !
+            _perms = ('repository.admin',)
+            if not HasRepoPermissionAnyApi(*_perms)(
+                    user=apiuser, repo_name=repo.repo_name):
+                raise JSONRPCError('repository `%s` does not exist' % (repoid,))
+
+            # check if we have at least read permission for this user group !
+            _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
+            if not HasUserGroupPermissionAny(*_perms)(
+                    user=apiuser, user_group_name=user_group.users_group_name):
+                raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
         try:
-            RepoModel().grant_users_group_permission(repo=repo,
-                                                     group_name=users_group,
-                                                     perm=perm)
+            RepoModel().grant_user_group_permission(
+                repo=repo, group_name=user_group, perm=perm)
 
             Session().commit()
             return dict(
                 msg='Granted perm: `%s` for user group: `%s` in '
                     'repo: `%s`' % (
-                    perm.permission_name, users_group.users_group_name,
-                    repo.repo_name
+                        perm.permission_name, user_group.users_group_name,
+                        repo.repo_name
+                    ),
+                success=True
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError(
+                'failed to edit permission for user group: `%s` in '
+                'repo: `%s`' % (
+                    usergroupid, repo.repo_name
+                )
+            )
+
+    # permission check inside
+    def revoke_user_group_permission(self, apiuser, repoid, usergroupid):
+        """
+        Revoke permission for user group on given repository. This command can be
+        executed only using api_key belonging to user with admin rights.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repoid: repository name or repository id
+        :type repoid: str or int
+        :param usergroupid:
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
+                      "success": true
+                    }
+            error:  null
+        """
+        repo = get_repo_or_error(repoid)
+        user_group = get_user_group_or_error(usergroupid)
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have admin permission for this repo !
+            _perms = ('repository.admin',)
+            if not HasRepoPermissionAnyApi(*_perms)(
+                    user=apiuser, repo_name=repo.repo_name):
+                raise JSONRPCError('repository `%s` does not exist' % (repoid,))
+
+            # check if we have at least read permission for this user group !
+            _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
+            if not HasUserGroupPermissionAny(*_perms)(
+                    user=apiuser, user_group_name=user_group.users_group_name):
+                raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
+
+        try:
+            RepoModel().revoke_user_group_permission(
+                repo=repo, group_name=user_group)
+
+            Session().commit()
+            return dict(
+                msg='Revoked perm for user group: `%s` in repo: `%s`' % (
+                    user_group.users_group_name, repo.repo_name
                 ),
                 success=True
             )
@@ -1119,46 +1967,579 @@
             raise JSONRPCError(
                 'failed to edit permission for user group: `%s` in '
                 'repo: `%s`' % (
-                    usersgroupid, repo.repo_name
+                    user_group.users_group_name, repo.repo_name
                 )
             )
 
     @HasPermissionAllDecorator('hg.admin')
-    def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
+    def get_repo_group(self, apiuser, repogroupid):
+        """
+        Returns given repo group together with permissions, and repositories
+        inside the group
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repogroupid: id/name of repository group
+        :type repogroupid: str or int
+        """
+        repo_group = get_repo_group_or_error(repogroupid)
+
+        members = []
+        for user in repo_group.repo_group_to_perm:
+            perm = user.permission.permission_name
+            user = user.user
+            user_data = {
+                'name': user.username,
+                'type': "user",
+                'permission': perm
+            }
+            members.append(user_data)
+
+        for user_group in repo_group.users_group_to_perm:
+            perm = user_group.permission.permission_name
+            user_group = user_group.users_group
+            user_group_data = {
+                'name': user_group.users_group_name,
+                'type': "user_group",
+                'permission': perm
+            }
+            members.append(user_group_data)
+
+        data = repo_group.get_api_data()
+        data["members"] = members
+        return data
+
+    @HasPermissionAllDecorator('hg.admin')
+    def get_repo_groups(self, apiuser):
         """
-        Revoke permission for user group on given repository
+        Returns all repository groups
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        """
+        result = []
+        for repo_group in RepoGroupModel().get_all():
+            result.append(repo_group.get_api_data())
+        return result
+
+    @HasPermissionAllDecorator('hg.admin')
+    def create_repo_group(self, apiuser, group_name, description=Optional(''),
+                          owner=Optional(OAttr('apiuser')),
+                          parent=Optional(None),
+                          copy_permissions=Optional(False)):
+        """
+        Creates a repository group. This command can be executed only using
+        api_key belonging to user with admin rights.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param group_name:
+        :type group_name:
+        :param description:
+        :type description:
+        :param owner:
+        :type owner:
+        :param parent:
+        :type parent:
+        :param copy_permissions:
+        :type copy_permissions:
+
+        OUTPUT::
+
+          id : <id_given_in_input>
+          result : {
+              "msg": "created new repo group `<repo_group_name>`"
+              "repo_group": <repogroup_object>
+          }
+          error :  null
+
+        ERROR OUTPUT::
 
-        :param apiuser:
-        :param repoid:
-        :param usersgroupid:
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            failed to create repo group `<repogroupid>`
+          }
+
         """
-        repo = get_repo_or_error(repoid)
-        users_group = get_users_group_or_error(usersgroupid)
+        if RepoGroup.get_by_group_name(group_name):
+            raise JSONRPCError("repo group `%s` already exist" % (group_name,))
+
+        if isinstance(owner, Optional):
+            owner = apiuser.user_id
+        group_description = Optional.extract(description)
+        parent_group = Optional.extract(parent)
+        if not isinstance(parent, Optional):
+            parent_group = get_repo_group_or_error(parent_group)
+
+        copy_permissions = Optional.extract(copy_permissions)
+        try:
+            repo_group = RepoGroupModel().create(
+                group_name=group_name,
+                group_description=group_description,
+                owner=owner,
+                parent=parent_group,
+                copy_permissions=copy_permissions
+            )
+            Session().commit()
+            return dict(
+                msg='created new repo group `%s`' % group_name,
+                repo_group=repo_group.get_api_data()
+            )
+        except Exception:
+
+            log.error(traceback.format_exc())
+            raise JSONRPCError('failed to create repo group `%s`' % (group_name,))
+
+    @HasPermissionAllDecorator('hg.admin')
+    def update_repo_group(self, apiuser, repogroupid, group_name=Optional(''),
+                          description=Optional(''),
+                          owner=Optional(OAttr('apiuser')),
+                          parent=Optional(None), enable_locking=Optional(False)):
+        repo_group = get_repo_group_or_error(repogroupid)
+
+        updates = {}
+        try:
+            store_update(updates, group_name, 'group_name')
+            store_update(updates, description, 'group_description')
+            store_update(updates, owner, 'owner')
+            store_update(updates, parent, 'parent_group')
+            store_update(updates, enable_locking, 'enable_locking')
+            repo_group = RepoGroupModel().update(repo_group, updates)
+            Session().commit()
+            return dict(
+                msg='updated repository group ID:%s %s' % (repo_group.group_id,
+                                                           repo_group.group_name),
+                repo_group=repo_group.get_api_data()
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError('failed to update repository group `%s`'
+                               % (repogroupid,))
+
+    @HasPermissionAllDecorator('hg.admin')
+    def delete_repo_group(self, apiuser, repogroupid):
+        """
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repogroupid: name or id of repository group
+        :type repogroupid: str or int
+
+        OUTPUT::
+
+          id : <id_given_in_input>
+          result : {
+            'msg': 'deleted repo group ID:<repogroupid> <repogroupname>
+            'repo_group': null
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to delete repo group ID:<repogroupid> <repogroupname>"
+          }
+
+        """
+        repo_group = get_repo_group_or_error(repogroupid)
 
         try:
-            RepoModel().revoke_users_group_permission(repo=repo,
-                                                      group_name=users_group)
-
+            RepoGroupModel().delete(repo_group)
             Session().commit()
             return dict(
-                msg='Revoked perm for user group: `%s` in repo: `%s`' % (
-                    users_group.users_group_name, repo.repo_name
+                msg='deleted repo group ID:%s %s' %
+                    (repo_group.group_id, repo_group.group_name),
+                repo_group=None
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError('failed to delete repo group ID:%s %s' %
+                               (repo_group.group_id, repo_group.group_name)
+            )
+
+    # permission check inside
+    def grant_user_permission_to_repo_group(self, apiuser, repogroupid, userid,
+                                            perm, apply_to_children=Optional('none')):
+        """
+        Grant permission for user on given repository group, or update existing
+        one if found. This command can be executed only using api_key belonging
+        to user with admin rights, or user who has admin right to given repository
+        group.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repogroupid: name or id of repository group
+        :type repogroupid: str or int
+        :param userid:
+        :param perm: (group.(none|read|write|admin))
+        :type perm: str
+        :param apply_to_children: 'none', 'repos', 'groups', 'all'
+        :type apply_to_children: str
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
+                      "success": true
+                    }
+            error:  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
+          }
+
+        """
+
+        repo_group = get_repo_group_or_error(repogroupid)
+
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have admin permission for this repo group !
+            if not HasRepoGroupPermissionAnyApi('group.admin')(user=apiuser,
+                                                               group_name=repo_group.group_name):
+                raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
+
+        user = get_user_or_error(userid)
+        perm = get_perm_or_error(perm, prefix='group.')
+        apply_to_children = Optional.extract(apply_to_children)
+
+        try:
+            RepoGroupModel().add_permission(repo_group=repo_group,
+                                            obj=user,
+                                            obj_type="user",
+                                            perm=perm,
+                                            recursive=apply_to_children)
+            Session().commit()
+            return dict(
+                msg='Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
+                    perm.permission_name, apply_to_children, user.username, repo_group.name
                 ),
                 success=True
             )
         except Exception:
             log.error(traceback.format_exc())
             raise JSONRPCError(
+                'failed to edit permission for user: `%s` in repo group: `%s`' % (
+                    userid, repo_group.name))
+
+    # permission check inside
+    def revoke_user_permission_from_repo_group(self, apiuser, repogroupid, userid,
+                                               apply_to_children=Optional('none')):
+        """
+        Revoke permission for user on given repository group. This command can
+        be executed only using api_key belonging to user with admin rights, or
+        user who has admin right to given repository group.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repogroupid: name or id of repository group
+        :type repogroupid: str or int
+        :param userid:
+        :type userid:
+        :param apply_to_children: 'none', 'repos', 'groups', 'all'
+        :type apply_to_children: str
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
+                      "success": true
+                    }
+            error:  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
+          }
+
+        """
+
+        repo_group = get_repo_group_or_error(repogroupid)
+
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have admin permission for this repo group !
+            if not HasRepoGroupPermissionAnyApi('group.admin')(user=apiuser,
+                                                               group_name=repo_group.group_name):
+                raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
+
+        user = get_user_or_error(userid)
+        apply_to_children = Optional.extract(apply_to_children)
+
+        try:
+            RepoGroupModel().delete_permission(repo_group=repo_group,
+                                               obj=user,
+                                               obj_type="user",
+                                               recursive=apply_to_children)
+
+            Session().commit()
+            return dict(
+                msg='Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
+                    apply_to_children, user.username, repo_group.name
+                ),
+                success=True
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError(
+                'failed to edit permission for user: `%s` in repo group: `%s`' % (
+                    userid, repo_group.name))
+
+    # permission check inside
+    def grant_user_group_permission_to_repo_group(
+            self, apiuser, repogroupid, usergroupid, perm,
+            apply_to_children=Optional('none'),):
+        """
+        Grant permission for user group on given repository group, or update
+        existing one if found. This command can be executed only using
+        api_key belonging to user with admin rights, or user who has admin
+        right to given repository group.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repogroupid: name or id of repository group
+        :type repogroupid: str or int
+        :param usergroupid: id of usergroup
+        :type usergroupid: str or int
+        :param perm: (group.(none|read|write|admin))
+        :type perm: str
+        :param apply_to_children: 'none', 'repos', 'groups', 'all'
+        :type apply_to_children: str
+
+        OUTPUT::
+
+          id : <id_given_in_input>
+          result : {
+            "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
+            "success": true
+
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
+          }
+
+        """
+        repo_group = get_repo_group_or_error(repogroupid)
+        perm = get_perm_or_error(perm, prefix='group.')
+        user_group = get_user_group_or_error(usergroupid)
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have admin permission for this repo group !
+            _perms = ('group.admin',)
+            if not HasRepoGroupPermissionAnyApi(*_perms)(
+                    user=apiuser, group_name=repo_group.group_name):
+                raise JSONRPCError(
+                    'repository group `%s` does not exist' % (repogroupid,))
+
+            # check if we have at least read permission for this user group !
+            _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
+            if not HasUserGroupPermissionAny(*_perms)(
+                    user=apiuser, user_group_name=user_group.users_group_name):
+                raise JSONRPCError(
+                    'user group `%s` does not exist' % (usergroupid,))
+
+        apply_to_children = Optional.extract(apply_to_children)
+
+        try:
+            RepoGroupModel().add_permission(repo_group=repo_group,
+                                            obj=user_group,
+                                            obj_type="user_group",
+                                            perm=perm,
+                                            recursive=apply_to_children)
+            Session().commit()
+            return dict(
+                msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
+                        perm.permission_name, apply_to_children,
+                        user_group.users_group_name, repo_group.name
+                    ),
+                success=True
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError(
                 'failed to edit permission for user group: `%s` in '
-                'repo: `%s`' % (
-                    users_group.users_group_name, repo.repo_name
+                'repo group: `%s`' % (
+                    usergroupid, repo_group.name
                 )
             )
 
+    # permission check inside
+    def revoke_user_group_permission_from_repo_group(
+            self, apiuser, repogroupid, usergroupid,
+            apply_to_children=Optional('none')):
+        """
+        Revoke permission for user group on given repository. This command can be
+        executed only using api_key belonging to user with admin rights, or
+        user who has admin right to given repository group.
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param repogroupid: name or id of repository group
+        :type repogroupid: str or int
+        :param usergroupid:
+        :param apply_to_children: 'none', 'repos', 'groups', 'all'
+        :type apply_to_children: str
+
+        OUTPUT::
+
+            id : <id_given_in_input>
+            result: {
+                      "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
+                      "success": true
+                    }
+            error:  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
+          }
+
+
+        """
+        repo_group = get_repo_group_or_error(repogroupid)
+        user_group = get_user_group_or_error(usergroupid)
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # check if we have admin permission for this repo group !
+            _perms = ('group.admin',)
+            if not HasRepoGroupPermissionAnyApi(*_perms)(
+                    user=apiuser, group_name=repo_group.group_name):
+                raise JSONRPCError(
+                    'repository group `%s` does not exist' % (repogroupid,))
+
+            # check if we have at least read permission for this user group !
+            _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
+            if not HasUserGroupPermissionAny(*_perms)(
+                    user=apiuser, user_group_name=user_group.users_group_name):
+                raise JSONRPCError(
+                    'user group `%s` does not exist' % (usergroupid,))
+
+        apply_to_children = Optional.extract(apply_to_children)
+
+        try:
+            RepoGroupModel().delete_permission(repo_group=repo_group,
+                                               obj=user_group,
+                                               obj_type="user_group",
+                                               recursive=apply_to_children)
+            Session().commit()
+            return dict(
+                msg='Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
+                    apply_to_children, user_group.users_group_name, repo_group.name
+                ),
+                success=True
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError(
+                'failed to edit permission for user group: `%s` in repo group: `%s`' % (
+                    user_group.users_group_name, repo_group.name
+                )
+            )
+
+    def get_gist(self, apiuser, gistid):
+        """
+        Get given gist by id
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param gistid: id of private or public gist
+        :type gistid: str
+        """
+        gist = get_gist_or_error(gistid)
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            if gist.gist_owner != apiuser.user_id:
+                raise JSONRPCError('gist `%s` does not exist' % (gistid,))
+        return gist.get_api_data()
+
+    def get_gists(self, apiuser, userid=Optional(OAttr('apiuser'))):
+        """
+        Get all gists for given user. If userid is empty returned gists
+        are for user who called the api
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param userid: user to get gists for
+        :type userid: Optional(str or int)
+        """
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            # make sure normal user does not pass someone else userid,
+            # he is not allowed to do that
+            if not isinstance(userid, Optional) and userid != apiuser.user_id:
+                raise JSONRPCError(
+                    'userid is not the same as your user'
+                )
+
+        if isinstance(userid, Optional):
+            user_id = apiuser.user_id
+        else:
+            user_id = get_user_or_error(userid).user_id
+
+        gists = []
+        _gists = Gist().query()\
+            .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
+            .filter(Gist.gist_owner == user_id)\
+            .order_by(Gist.created_on.desc())
+        for gist in _gists:
+            gists.append(gist.get_api_data())
+        return gists
+
     def create_gist(self, apiuser, files, owner=Optional(OAttr('apiuser')),
                     gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
                     description=Optional('')):
 
+        """
+        Creates new Gist
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param files: files to be added to gist
+            {'filename': {'content':'...', 'lexer': null},
+             'filename2': {'content':'...', 'lexer': null}}
+        :type files: dict
+        :param owner: gist owner, defaults to api method caller
+        :type owner: Optional(str or int)
+        :param gist_type: type of gist 'public' or 'private'
+        :type gist_type: Optional(str)
+        :param lifetime: time in minutes of gist lifetime
+        :type lifetime: Optional(int)
+        :param description: gist description
+        :type description: Optional(str)
+
+        OUTPUT::
+
+          id : <id_given_in_input>
+          result : {
+            "msg": "created new gist",
+            "gist": {}
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to create gist"
+          }
+
+        """
         try:
             if isinstance(owner, Optional):
                 owner = apiuser.user_id
@@ -1168,10 +2549,6 @@
             gist_type = Optional.extract(gist_type)
             lifetime = Optional.extract(lifetime)
 
-            # files: {
-            #    'filename': {'content':'...', 'lexer': null},
-            #    'filename2': {'content':'...', 'lexer': null}
-            #}
             gist = GistModel().create(description=description,
                                       owner=owner,
                                       gist_mapping=files,
@@ -1185,3 +2562,54 @@
         except Exception:
             log.error(traceback.format_exc())
             raise JSONRPCError('failed to create gist')
+
+    # def update_gist(self, apiuser, gistid, files, owner=Optional(OAttr('apiuser')),
+    #                 gist_type=Optional(Gist.GIST_PUBLIC),
+    #                 gist_lifetime=Optional(-1), gist_description=Optional('')):
+    #     gist = get_gist_or_error(gistid)
+    #     updates = {}
+
+    # permission check inside
+    def delete_gist(self, apiuser, gistid):
+        """
+        Deletes existing gist
+
+        :param apiuser: filled automatically from apikey
+        :type apiuser: AuthUser
+        :param gistid: id of gist to delete
+        :type gistid: str
+
+        OUTPUT::
+
+          id : <id_given_in_input>
+          result : {
+            "deleted gist ID: <gist_id>",
+            "gist": null
+          }
+          error :  null
+
+        ERROR OUTPUT::
+
+          id : <id_given_in_input>
+          result : null
+          error :  {
+            "failed to delete gist ID:<gist_id>"
+          }
+
+        """
+        gist = get_gist_or_error(gistid)
+        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+            if gist.gist_owner != apiuser.user_id:
+                raise JSONRPCError('gist `%s` does not exist' % (gistid,))
+
+        try:
+            GistModel().delete(gist)
+            Session().commit()
+            return dict(
+                msg='deleted gist ID:%s' % (gist.gist_access_id,),
+                gist=None
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError('failed to delete gist ID:%s'
+                               % (gist.gist_access_id,))
--- a/rhodecode/controllers/bookmarks.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/bookmarks.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.bookmarks
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Bookmarks controller for rhodecode
-
-    :created_on: Dec 1, 2011
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.bookmarks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Bookmarks controller for rhodecode
+
+:created_on: Dec 1, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import logging
 
 from pylons import tmpl_context as c
--- a/rhodecode/controllers/branches.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/branches.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.branches
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    branches controller for rhodecode
-
-    :created_on: Apr 21, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.branches
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+branches controller for rhodecode
+
+:created_on: Apr 21, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import logging
 import binascii
--- a/rhodecode/controllers/changelog.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/changelog.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.changelog
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    changelog controller for rhodecode
-
-    :created_on: Apr 21, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.changelog
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+changelog controller for rhodecode
+
+:created_on: Apr 21, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import logging
 import traceback
--- a/rhodecode/controllers/changeset.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/changeset.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.changeset
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    changeset controller for pylons showoing changes beetween
-    revisions
-
-    :created_on: Apr 25, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,6 +11,19 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.changeset
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+changeset controller for pylons showoing changes beetween
+revisions
+
+:created_on: Apr 25, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import logging
 import traceback
 from collections import defaultdict
@@ -161,7 +162,7 @@
     if ig_ws:
         params[ig_ws_key] += [ig_ws_val]
 
-    lbl = _('%s line context') % ln_ctx
+    lbl = _('increase diff context to %(num)s lines') % {'num': ln_ctx}
 
     params['anchor'] = fileid
     img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
@@ -173,9 +174,11 @@
     def __before__(self):
         super(ChangesetController, self).__before__()
         c.affected_files_cut_off = 60
+
+    def __load_data(self):
         repo_model = RepoModel()
         c.users_array = repo_model.get_users_js()
-        c.users_groups_array = repo_model.get_users_groups_js()
+        c.user_groups_array = repo_model.get_user_groups_js()
 
     def _index(self, revision, method):
         c.anchor_url = anchor_url
@@ -199,9 +202,13 @@
             if not c.cs_ranges:
                 raise RepositoryError('Changeset range returned empty result')
 
-        except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
+        except(ChangesetDoesNotExistError,), e:
             log.error(traceback.format_exc())
-            h.flash(safe_str(e), category='error')
+            msg = _('Such revision does not exist for this repository')
+            h.flash(msg, category='error')
+            raise HTTPNotFound()
+        except (Exception,), e:
+            log.error(traceback.format_exc())
             raise HTTPNotFound()
 
         c.changes = OrderedDict()
@@ -303,6 +310,7 @@
             response.content_type = 'text/plain'
             return diff
         elif method == 'show':
+            self.__load_data()
             if len(c.cs_ranges) == 1:
                 return render('changeset/changeset.html')
             else:
@@ -418,7 +426,8 @@
     def delete_comment(self, repo_name, comment_id):
         co = ChangesetComment.get(comment_id)
         owner = co.author.user_id == c.rhodecode_user.user_id
-        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
+        repo_admin = h.HasRepoPermissionAny('repository.admin')
+        if h.HasPermissionAny('hg.admin')() or repo_admin or owner:
             ChangesetCommentsModel().delete(comment=co)
             Session().commit()
             return True
@@ -437,3 +446,31 @@
                 return EmptyChangeset(message=str(e))
         else:
             raise HTTPBadRequest()
+
+    @LoginRequired()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    @jsonify
+    def changeset_children(self, repo_name, revision):
+        if request.is_xhr:
+            changeset = c.rhodecode_repo.get_changeset(revision)
+            result = {"results": []}
+            if changeset.children:
+                result = {"results": changeset.children}
+            return result
+        else:
+            raise HTTPBadRequest()
+
+    @LoginRequired()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    @jsonify
+    def changeset_parents(self, repo_name, revision):
+        if request.is_xhr:
+            changeset = c.rhodecode_repo.get_changeset(revision)
+            result = {"results": []}
+            if changeset.parents:
+                result = {"results": changeset.parents}
+            return result
+        else:
+            raise HTTPBadRequest()
--- a/rhodecode/controllers/compare.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/compare.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.compare
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    compare controller for pylons showing differences between two
-    repos, branches, bookmarks or tips
-
-    :created_on: May 6, 2012
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,6 +11,19 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.compare
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+compare controller for pylons showing differences between two
+repos, branches, bookmarks or tips
+
+:created_on: May 6, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import logging
 import traceback
@@ -35,12 +36,11 @@
 
 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
 from rhodecode.lib.vcs.utils import safe_str
-from rhodecode.lib.vcs.utils.hgcompat import scmutil, unionrepo
+from rhodecode.lib.vcs.utils.hgcompat import unionrepo
 from rhodecode.lib import helpers as h
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib import diffs
-from rhodecode.lib.utils2 import safe_str
 from rhodecode.model.db import Repository
 from rhodecode.lib.diffs import LimitedDiffContainer
 
@@ -54,16 +54,17 @@
         super(CompareController, self).__before__()
 
     def __get_rev_or_redirect(self, ref, repo, redirect_after=True,
-                             partial=False):
+                              partial=False):
         """
         Safe way to get changeset if error occur it redirects to changeset with
         proper message. If partial is set then don't do redirect raise Exception
         instead
 
-        :param rev: revision to fetch
-        :param repo: repo instance
+        :param ref:
+        :param repo:
+        :param redirect_after:
+        :param partial:
         """
-
         rev = ref[1] # default and used for git
         if repo.scm_instance.alias == 'hg':
             # lookup up the exact node id
@@ -97,12 +98,17 @@
 
     def _get_changesets(self, alias, org_repo, org_rev, other_repo, other_rev, merge):
         """
-        Returns a list of changesets that can be merged from org_repo@org_rev
-        to other_repo@other_rev ... and the ancestor that would be used for merge
+        Returns a list of changesets that can be merged from org_repo at org_rev
+        to other_repo at other_rev ... and the ancestor that would be used for merge.
+
+        :param org_repo: repo object, that is most likely the orginal repo we forked from
+        :param org_rev: the revision we want our compare to be made
+        :param other_repo: repo object, mostl likely the fork of org_repo. It hass
+            all changesets that we need to obtain
+        :param other_rev: revision we want out compare to be made on other_repo
+
         """
-
         ancestor = None
-
         if org_rev == other_rev:
             changesets = []
             if merge:
@@ -122,10 +128,12 @@
                 hgrepo = other_repo._repo
 
             if merge:
-                revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
-                                   other_rev, org_rev, org_rev)
+                revs = hgrepo.revs(
+                    "ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
+                    other_rev, org_rev, org_rev)
 
-                ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev)
+                ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev,
+                                        other_rev)
                 if ancestors:
                     # pick arbitrary ancestor - but there is usually only one
                     ancestor = hgrepo[ancestors[0]].hex()
@@ -138,22 +146,56 @@
 
         elif alias == 'git':
             if org_repo != other_repo:
-                raise Exception('Comparing of different GIT repositories is not'
-                                'allowed. Got %s != %s' % (org_repo, other_repo))
+                from dulwich.repo import Repo
+                from dulwich.client import SubprocessGitClient
+
+                gitrepo = Repo(org_repo.path)
+                SubprocessGitClient(thin_packs=False).fetch(other_repo.path, gitrepo)
+
+                gitrepo_remote = Repo(other_repo.path)
+                SubprocessGitClient(thin_packs=False).fetch(org_repo.path, gitrepo_remote)
+
+                revs = []
+                for x in gitrepo_remote.get_walker(include=[other_rev],
+                                                   exclude=[org_rev]):
+                    revs.append(x.commit.id)
 
-            so, se = org_repo.run_git_command(
-                'log --reverse --pretty="format: %%H" -s -p %s..%s'
-                    % (org_rev, other_rev)
-            )
-            changesets = [org_repo.get_changeset(cs)
-                          for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
+                changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)]
+                if changesets:
+                    ancestor = changesets[0].parents[0].raw_id
+                else:
+                    # no changesets from other repo, ancestor is the other_rev
+                    ancestor = other_rev
+
+            else:
+                so, se = org_repo.run_git_command(
+                    'log --reverse --pretty="format: %%H" -s %s..%s'
+                        % (org_rev, other_rev)
+                )
+                changesets = [org_repo.get_changeset(cs)
+                              for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
+
+        else:
+            raise Exception('Bad alias only git and hg is allowed')
 
         return changesets, ancestor
 
     @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
-    def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
+    def index(self, repo_name):
+        c.compare_home = True
+        org_repo = c.rhodecode_db_repo.repo_name
+        other_repo = request.GET.get('other_repo', org_repo)
+        c.org_repo = Repository.get_by_repo_name(org_repo)
+        c.other_repo = Repository.get_by_repo_name(other_repo)
+        c.org_ref = c.other_ref = _('Select changeset')
+        return render('compare/compare_diff.html')
+
+    @LoginRequired()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    def compare(self, repo_name, org_ref_type, org_ref, other_ref_type, other_ref):
         # org_ref will be evaluated in org_repo
         org_repo = c.rhodecode_db_repo.repo_name
         org_ref = (org_ref_type, org_ref)
@@ -187,23 +229,27 @@
         other_repo = Repository.get_by_repo_name(other_repo)
 
         if org_repo is None:
-            log.error('Could not find org repo %s' % org_repo)
-            raise HTTPNotFound
+            msg = 'Could not find org repo %s' % org_repo
+            log.error(msg)
+            h.flash(msg, category='error')
+            return redirect(url('compare_home', repo_name=c.repo_name))
+
         if other_repo is None:
-            log.error('Could not find other repo %s' % other_repo)
-            raise HTTPNotFound
-
-        if org_repo != other_repo and h.is_git(org_repo):
-            log.error('compare of two remote repos not available for GIT REPOS')
-            raise HTTPNotFound
+            msg = 'Could not find other repo %s' % other_repo
+            log.error(msg)
+            h.flash(msg, category='error')
+            return redirect(url('compare_home', repo_name=c.repo_name))
 
         if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
-            log.error('compare of two different kind of remote repos not available')
-            raise HTTPNotFound
+            msg = 'compare of two different kind of remote repos not available'
+            log.error(msg)
+            h.flash(msg, category='error')
+            return redirect(url('compare_home', repo_name=c.repo_name))
 
         org_rev = self.__get_rev_or_redirect(ref=org_ref, repo=org_repo, partial=partial)
         other_rev = self.__get_rev_or_redirect(ref=other_ref, repo=other_repo, partial=partial)
 
+        c.compare_home = False
         c.org_repo = org_repo
         c.other_repo = other_repo
         c.org_ref = org_ref[1]
@@ -211,19 +257,17 @@
         c.org_ref_type = org_ref[0]
         c.other_ref_type = other_ref[0]
 
-        c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
-                                                       org_repo.scm_instance, org_rev,
-                                                       other_repo.scm_instance, other_rev,
-                                                       merge)
+        c.cs_ranges, c.ancestor = self._get_changesets(
+            org_repo.scm_instance.alias, org_repo.scm_instance, org_rev,
+            other_repo.scm_instance, other_rev, merge)
+        c.statuses = c.rhodecode_db_repo.statuses(
+            [x.raw_id for x in c.cs_ranges])
 
-        c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
-                                                   c.cs_ranges])
         if merge and not c.ancestor:
             log.error('Unable to find ancestor revision')
 
         if partial:
             return render('compare/compare_cs.html')
-
         if c.ancestor:
             assert merge
             # case we want a simple diff without incoming changesets,
@@ -238,8 +282,8 @@
 
         log.debug('running diff between %s and %s in %s'
                   % (org_rev, other_rev, org_repo.scm_instance.path))
+
         txtdiff = org_repo.scm_instance.get_diff(rev1=org_rev, rev2=other_rev)
-
         diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
                                              diff_limit=diff_limit)
         _parsed = diff_processor.prepare()
--- a/rhodecode/controllers/error.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/error.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.error
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    RhodeCode error controller
-
-    :created_on: Dec 8, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.error
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+RhodeCode error controller
+
+:created_on: Dec 8, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import os
 import cgi
 import logging
--- a/rhodecode/controllers/feed.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/feed.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.feed
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Feed controller for rhodecode
-
-    :created_on: Apr 23, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.feed
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Feed controller for rhodecode
+
+:created_on: Apr 23, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import logging
 
--- a/rhodecode/controllers/files.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/files.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.files
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Files controller for RhodeCode
-
-    :created_on: Apr 21, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Files controller for RhodeCode
+
+:created_on: Apr 21, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 from __future__ import with_statement
 import os
 import logging
@@ -85,13 +86,17 @@
                 return None
             url_ = url('files_add_home',
                        repo_name=c.repo_name,
-                       revision=0, f_path='')
-            add_new = h.link_to(_('Click here to add new file'), url_)
-            h.flash(h.literal(_('There are no files yet %s') % add_new),
+                       revision=0, f_path='', anchor='edit')
+            add_new = h.link_to(_('Click here to add new file'), url_, class_="alert-link")
+            h.flash(h.literal(_('There are no files yet. %s') % add_new),
                     category='warning')
             redirect(h.url('summary_home', repo_name=repo_name))
-
-        except RepositoryError, e:  # including ChangesetDoesNotExistError
+        except(ChangesetDoesNotExistError, LookupError), e:
+            log.error(traceback.format_exc())
+            msg = _('Such revision does not exist for this repository')
+            h.flash(msg, category='error')
+            raise HTTPNotFound()
+        except RepositoryError, e:
             h.flash(safe_str(e), category='error')
             raise HTTPNotFound()
 
@@ -109,6 +114,11 @@
             file_node = cs.get_node(path)
             if file_node.is_dir():
                 raise RepositoryError('given path is a directory')
+        except(ChangesetDoesNotExistError,), e:
+            log.error(traceback.format_exc())
+            msg = _('Such revision does not exist for this repository')
+            h.flash(msg, category='error')
+            raise HTTPNotFound()
         except RepositoryError, e:
             h.flash(safe_str(e), category='error')
             raise HTTPNotFound()
@@ -186,22 +196,40 @@
     @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
-    def history(self, repo_name, revision, f_path, annotate=False):
-        if request.environ.get('HTTP_X_PARTIAL_XHR'):
-            c.changeset = self.__get_cs_or_redirect(revision, repo_name)
-            c.f_path = f_path
-            c.annotate = annotate
-            c.file = c.changeset.get_node(f_path)
-            if c.file.is_file():
-                file_last_cs = c.file.last_changeset
-                c.file_changeset = (c.changeset
-                                    if c.changeset.revision < file_last_cs.revision
-                                    else file_last_cs)
-                c.file_history, _hist = self._get_node_history(c.changeset, f_path)
-                c.authors = []
-                for a in set([x.author for x in _hist]):
-                    c.authors.append((h.email(a), h.person(a)))
-                return render('files/files_history_box.html')
+    @jsonify
+    def history(self, repo_name, revision, f_path):
+        changeset = self.__get_cs_or_redirect(revision, repo_name)
+        f_path = f_path
+        _file = changeset.get_node(f_path)
+        if _file.is_file():
+            file_history, _hist = self._get_node_history(changeset, f_path)
+
+            res = []
+            for obj in file_history:
+                res.append({
+                    'text': obj[1],
+                    'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
+                })
+
+            data = {
+                'more': False,
+                'results': res
+            }
+            return data
+
+    @LoginRequired()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    def authors(self, repo_name, revision, f_path):
+        changeset = self.__get_cs_or_redirect(revision, repo_name)
+        f_path = f_path
+        _file = changeset.get_node(f_path)
+        if _file.is_file():
+            file_history, _hist = self._get_node_history(changeset, f_path)
+            c.authors = []
+            for a in set([x.author for x in _hist]):
+                c.authors.append((h.email(a), h.person(a)))
+            return render('files/files_history_box.html')
 
     @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
@@ -265,6 +293,66 @@
 
     @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+    def delete(self, repo_name, revision, f_path):
+        repo = c.rhodecode_db_repo
+        if repo.enable_locking and repo.locked[0]:
+            h.flash(_('This repository is has been locked by %s on %s')
+                % (h.person_by_id(repo.locked[0]),
+                   h.fmt_date(h.time_to_datetime(repo.locked[1]))),
+                'warning')
+            return redirect(h.url('files_home',
+                                  repo_name=repo_name, revision='tip'))
+
+        # check if revision is a branch identifier- basically we cannot
+        # create multiple heads via file editing
+        _branches = repo.scm_instance.branches
+        # check if revision is a branch name or branch hash
+        if revision not in _branches.keys() + _branches.values():
+            h.flash(_('You can only delete files with revision '
+                      'being a valid branch '), category='warning')
+            return redirect(h.url('files_home',
+                                  repo_name=repo_name, revision='tip',
+                                  f_path=f_path))
+
+        r_post = request.POST
+
+        c.cs = self.__get_cs_or_redirect(revision, repo_name)
+        c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
+
+        c.default_message = _('Deleted file %s via RhodeCode') % (f_path)
+        c.f_path = f_path
+        node_path = f_path
+        author = self.rhodecode_user.full_contact
+
+        if r_post:
+            message = r_post.get('message') or c.default_message
+
+            try:
+                nodes = {
+                    node_path: {
+                        'content': ''
+                    }
+                }
+                self.scm_model.delete_nodes(
+                    user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
+                    message=message,
+                    nodes=nodes,
+                    parent_cs=c.cs,
+                    author=author,
+                )
+
+                h.flash(_('Successfully deleted file %s') % f_path,
+                        category='success')
+            except Exception:
+                log.error(traceback.format_exc())
+                h.flash(_('Error occurred during commit'), category='error')
+            return redirect(url('changeset_home',
+                                repo_name=c.repo_name, revision='tip'))
+
+        return render('files/files_delete.html')
+
+    @LoginRequired()
+    @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
     def edit(self, repo_name, revision, f_path):
         repo = c.rhodecode_db_repo
         if repo.enable_locking and repo.locked[0]:
@@ -468,19 +556,21 @@
                 log.debug('Archive %s is not yet cached' % (archive_name))
 
         if not use_cached_archive:
-            #generate new archive
+            # generate new archive
+            temp_stream = None
             try:
                 fd, archive = tempfile.mkstemp()
-                t = open(archive, 'wb')
+                temp_stream = open(archive, 'wb')
                 log.debug('Creating new temp archive in %s' % archive)
-                cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
-                if archive_cache_enabled:
+                cs.fill_archive(stream=temp_stream, kind=fileformat, subrepos=subrepos)
+                if not subrepos and archive_cache_enabled:
                     #if we generated the archive and use cache rename that
                     log.debug('Storing new archive in %s' % cached_archive_path)
                     shutil.move(archive, cached_archive_path)
                     archive = cached_archive_path
             finally:
-                t.close()
+                if temp_stream:
+                    temp_stream.close()
 
         def get_chunked_archive(archive):
             stream = open(archive, 'rb')
@@ -654,28 +744,6 @@
             log.error(traceback.format_exc())
             return redirect(url('files_home', repo_name=c.repo_name,
                                 f_path=f_path))
-        if node2.is_binary:
-            node2_content = 'binary file'
-        else:
-            node2_content = node2.content
-
-        if node1.is_binary:
-            node1_content = 'binary file'
-        else:
-            node1_content = node1.content
-
-        html_escape_table = {
-            "&": "\u0026",
-            '"': "\u0022",
-            "'": "\u0027",
-            ">": "\u003e",
-            "<": "\u003c",
-            '\\': "\u005c",
-            '\n': '\\n'
-        }
-
-        c.orig1 = h.html_escape((node1_content), html_escape_table)
-        c.orig2 = h.html_escape((node2_content), html_escape_table)
         c.node1 = node1
         c.node2 = node2
         c.cs1 = c.changeset_1
--- a/rhodecode/controllers/followers.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/followers.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.followers
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Followers controller for rhodecode
-
-    :created_on: Apr 23, 2011
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.followers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Followers controller for rhodecode
+
+:created_on: Apr 23, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import logging
 
 from pylons import tmpl_context as c, request
--- a/rhodecode/controllers/forks.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/forks.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.forks
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    forks controller for rhodecode
-
-    :created_on: Apr 23, 2011
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.forks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+forks controller for rhodecode
+
+:created_on: Apr 23, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import logging
 import formencode
 import traceback
@@ -30,13 +31,13 @@
 from pylons import tmpl_context as c, request, url
 from pylons.controllers.util import redirect
 from pylons.i18n.translation import _
+from webob.exc import HTTPNotFound, HTTPInternalServerError
 
 import rhodecode.lib.helpers as h
 
 from rhodecode.lib.helpers import Page
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
-    NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
-    HasPermissionAnyDecorator
+    NotAnonymous, HasRepoPermissionAny, HasPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User,\
     RhodeCodeUi
@@ -44,6 +45,7 @@
 from rhodecode.model.forms import RepoForkForm
 from rhodecode.model.scm import ScmModel, RepoGroupList
 from rhodecode.lib.utils2 import safe_int
+from rhodecode.lib.utils import jsonify
 
 log = logging.getLogger(__name__)
 
@@ -160,6 +162,7 @@
                              repo_groups=c.repo_groups_choices,
                              landing_revs=c.landing_revs_choices)()
         form_result = {}
+        task_id = None
         try:
             form_result = _form.to_python(dict(request.POST))
 
@@ -169,16 +172,12 @@
 
             # create fork is done sometimes async on celery, db transaction
             # management is handled there.
-            RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
-            fork_url = h.link_to(form_result['repo_name_full'],
-                    h.url('summary_home', repo_name=form_result['repo_name_full']))
-
-            h.flash(h.literal(_('Forked repository %s as %s') \
-                      % (repo_name, fork_url)),
-                    category='success')
+            task = RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
+            from celery.result import BaseAsyncResult
+            if isinstance(task, BaseAsyncResult):
+                task_id = task.task_id
         except formencode.Invalid, errors:
             c.new_repo = errors.value['repo_name']
-
             return htmlfill.render(
                 render('forks/fork.html'),
                 defaults=errors.value,
@@ -190,4 +189,6 @@
             h.flash(_('An error occurred during repository forking %s') %
                     repo_name, category='error')
 
-        return redirect(h.url('summary_home', repo_name=repo_name))
+        return redirect(h.url('repo_creating_home',
+                              repo_name=form_result['repo_name_full'],
+                              task_id=task_id))
--- a/rhodecode/controllers/home.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/home.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.home
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Home controller for Rhodecode
-
-    :created_on: Feb 18, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.home
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Home controller for Rhodecode
+
+:created_on: Feb 18, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+"""
 
 import logging
 
@@ -32,10 +33,11 @@
 
 import rhodecode
 from rhodecode.lib import helpers as h
+from rhodecode.lib.utils import jsonify, conditional_cache
 from rhodecode.lib.compat import json
-from rhodecode.lib.auth import LoginRequired
+from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseController, render
-from rhodecode.model.db import Repository
+from rhodecode.model.db import Repository, RepoGroup
 from rhodecode.model.repo import RepoModel
 
 
@@ -49,7 +51,7 @@
 
     @LoginRequired()
     def index(self):
-        c.groups = self.scm_model.get_repos_groups()
+        c.groups = self.scm_model.get_repo_groups()
         c.group = None
 
         c.repos_list = Repository.query()\
@@ -65,21 +67,80 @@
         return render('/index.html')
 
     @LoginRequired()
-    def repo_switcher(self):
-        if request.is_xhr:
+    @jsonify
+    def repo_switcher_data(self):
+        #wrapper for conditional cache
+        def _c():
+            log.debug('generating switcher repo/groups list')
             all_repos = Repository.query().order_by(Repository.repo_name).all()
-            c.repos_list = self.scm_model.get_repos(all_repos,
-                                                    sort_key='name_sort',
-                                                    simple=True)
-            return render('/repo_switcher_list.html')
+            repo_iter = self.scm_model.get_repos(all_repos, simple=True)
+            all_groups = RepoGroup.query().order_by(RepoGroup.group_name).all()
+            repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
+
+            res = [{
+                    'text': _('Groups'),
+                    'children': [
+                       {'id': obj.group_name, 'text': obj.group_name,
+                        'type': 'group', 'obj': {}} for obj in repo_groups_iter]
+                   }, {
+                    'text': _('Repositories'),
+                    'children': [
+                       {'id': obj['name'], 'text': obj['name'],
+                        'type': 'repo', 'obj': obj['dbrepo']} for obj in repo_iter]
+                   }]
+
+            data = {
+                'more': False,
+                'results': res
+            }
+            return data
+
+        if request.is_xhr:
+            condition = False
+            compute = conditional_cache('short_term', 'cache_desc',
+                                        condition=condition, func=_c)
+            return compute()
         else:
             raise HTTPBadRequest()
 
     @LoginRequired()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
     def branch_tag_switcher(self, repo_name):
         if request.is_xhr:
-            c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
+            c.rhodecode_db_repo = Repository.get_by_repo_name(repo_name)
             if c.rhodecode_db_repo:
                 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
                 return render('/switch_to_list.html')
         raise HTTPBadRequest()
+
+    @LoginRequired()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    @jsonify
+    def repo_refs_data(self, repo_name):
+        repo = Repository.get_by_repo_name(repo_name).scm_instance
+        res = []
+        _branches = repo.branches.items()
+        if _branches:
+            res.append({
+                'text': _('Branch'),
+                'children': [{'id': rev, 'text': name, 'type': 'branch'} for name, rev in _branches]
+            })
+        _tags = repo.tags.items()
+        if _tags:
+            res.append({
+                'text': _('Tag'),
+                'children': [{'id': rev, 'text': name, 'type': 'tag'} for name, rev in _tags]
+            })
+        _bookmarks = repo.bookmarks.items()
+        if _bookmarks:
+            res.append({
+                'text': _('Bookmark'),
+                'children': [{'id': rev, 'text': name, 'type': 'book'} for name, rev in _bookmarks]
+            })
+        data = {
+            'more': False,
+            'results': res
+        }
+        return data
--- a/rhodecode/controllers/journal.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/journal.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.journal
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Journal controller for pylons
-
-    :created_on: Nov 21, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,19 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.journal
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Journal controller for pylons
+
+:created_on: Nov 21, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+"""
+
 import logging
 from itertools import groupby
 
@@ -230,8 +232,8 @@
         def quick_menu(repo_name):
             return _render('quick_menu', repo_name)
 
-        def repo_lnk(name, rtype, private, fork_of):
-            return _render('repo_name', name, rtype, private, fork_of,
+        def repo_lnk(name, rtype, rstate, private, fork_of):
+            return _render('repo_name', name, rtype, rstate, private, fork_of,
                            short_name=False, admin=False)
 
         def last_rev(repo_name, cs_cache):
@@ -262,9 +264,9 @@
                 "menu": quick_menu(repo.repo_name),
                 "raw_name": repo.repo_name.lower(),
                 "name": repo_lnk(repo.repo_name, repo.repo_type,
-                                 repo.private, repo.fork),
+                                 repo.repo_state, repo.private, repo.fork),
                 "last_changeset": last_rev(repo.repo_name, cs_cache),
-                "raw_tip": cs_cache.get('revision'),
+                "last_rev_raw": cs_cache.get('revision'),
                 "action": toogle_follow(repo.repo_id)
             }
 
--- a/rhodecode/controllers/login.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/login.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.login
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Login controller for rhodeocode
-
-    :created_on: Apr 22, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.login
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Login controller for rhodeocode
+
+:created_on: Apr 22, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import logging
 import formencode
@@ -31,14 +32,15 @@
 from formencode import htmlfill
 from webob.exc import HTTPFound
 from pylons.i18n.translation import _
-from pylons.controllers.util import abort, redirect
-from pylons import request, response, session, tmpl_context as c, url
+from pylons.controllers.util import redirect
+from pylons import request, session, tmpl_context as c, url
 
 import rhodecode.lib.helpers as h
 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
+from rhodecode.lib.auth_modules import importplugin
 from rhodecode.lib.base import BaseController, render
 from rhodecode.lib.exceptions import UserCreationError
-from rhodecode.model.db import User
+from rhodecode.model.db import User, RhodeCodeSetting
 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
 from rhodecode.model.user import UserModel
 from rhodecode.model.meta import Session
@@ -52,13 +54,63 @@
     def __before__(self):
         super(LoginController, self).__before__()
 
+    def _store_user_in_session(self, username, remember=False):
+        user = User.get_by_username(username, case_insensitive=True)
+        auth_user = AuthUser(user.user_id)
+        auth_user.set_authenticated()
+        cs = auth_user.get_cookie_store()
+        session['rhodecode_user'] = cs
+        user.update_lastlogin()
+        Session().commit()
+
+        # If they want to be remembered, update the cookie
+        if remember:
+            _year = (datetime.datetime.now() +
+                     datetime.timedelta(seconds=60 * 60 * 24 * 365))
+            session._set_cookie_expires(_year)
+
+        session.save()
+
+        log.info('user %s is now authenticated and stored in '
+                 'session, session attrs %s' % (username, cs))
+
+        # dumps session attrs back to cookie
+        session._update_cookie_out()
+        # we set new cookie
+        headers = None
+        if session.request['set_cookie']:
+            # send set-cookie headers back to response to update cookie
+            headers = [('Set-Cookie', session.request['cookie_out'])]
+        return headers
+
+    def _validate_came_from(self, came_from):
+        if not came_from:
+            return came_from
+
+        parsed = urlparse.urlparse(came_from)
+        server_parsed = urlparse.urlparse(url.current())
+        allowed_schemes = ['http', 'https']
+        if parsed.scheme and parsed.scheme not in allowed_schemes:
+            log.error('Suspicious URL scheme detected %s for url %s' %
+                     (parsed.scheme, parsed))
+            came_from = url('home')
+        elif server_parsed.netloc != parsed.netloc:
+            log.error('Suspicious NETLOC detected %s for url %s server url '
+                      'is: %s' % (parsed.netloc, parsed, server_parsed))
+            came_from = url('home')
+        return came_from
+
     def index(self):
+        _default_came_from = url('home')
+        came_from = self._validate_came_from(request.GET.get('came_from'))
+        c.came_from = came_from or _default_came_from
+
+        not_default = self.rhodecode_user.username != User.DEFAULT_USER
+        ip_allowed = self.rhodecode_user.ip_allowed
+
         # redirect if already logged in
-        c.came_from = request.GET.get('came_from')
-        not_default = self.rhodecode_user.username != 'default'
-        ip_allowed = self.rhodecode_user.ip_allowed
         if self.rhodecode_user.is_authenticated and not_default and ip_allowed:
-            return redirect(url('home'))
+            raise HTTPFound(location=c.came_from)
 
         if request.POST:
             # import Login Form validator class
@@ -67,53 +119,10 @@
                 session.invalidate()
                 c.form_result = login_form.to_python(dict(request.POST))
                 # form checks for username/password, now we're authenticated
-                username = c.form_result['username']
-                user = User.get_by_username(username, case_insensitive=True)
-                auth_user = AuthUser(user.user_id)
-                auth_user.set_authenticated()
-                cs = auth_user.get_cookie_store()
-                session['rhodecode_user'] = cs
-                user.update_lastlogin()
-                Session().commit()
-
-                # If they want to be remembered, update the cookie
-                if c.form_result['remember']:
-                    _year = (datetime.datetime.now() +
-                             datetime.timedelta(seconds=60 * 60 * 24 * 365))
-                    session._set_cookie_expires(_year)
-
-                session.save()
-
-                log.info('user %s is now authenticated and stored in '
-                         'session, session attrs %s' % (username, cs))
-
-                # dumps session attrs back to cookie
-                session._update_cookie_out()
-
-                # we set new cookie
-                headers = None
-                if session.request['set_cookie']:
-                    # send set-cookie headers back to response to update cookie
-                    headers = [('Set-Cookie', session.request['cookie_out'])]
-
-                allowed_schemes = ['http', 'https']
-                if c.came_from:
-                    parsed = urlparse.urlparse(c.came_from)
-                    server_parsed = urlparse.urlparse(url.current())
-                    if parsed.scheme and parsed.scheme not in allowed_schemes:
-                        log.error(
-                            'Suspicious URL scheme detected %s for url %s' %
-                            (parsed.scheme, parsed))
-                        c.came_from = url('home')
-                    elif server_parsed.netloc != parsed.netloc:
-                        log.error('Suspicious NETLOC detected %s for url %s'
-                                  'server url is: %s' %
-                                  (parsed.netloc, parsed, server_parsed))
-                        c.came_from = url('home')
-                    raise HTTPFound(location=c.came_from, headers=headers)
-                else:
-                    raise HTTPFound(location=url('home'), headers=headers)
-
+                headers = self._store_user_in_session(
+                                        username=c.form_result['username'],
+                                        remember=c.form_result['remember'])
+                raise HTTPFound(location=c.came_from, headers=headers)
             except formencode.Invalid, errors:
                 defaults = errors.value
                 # remove password from filling in form again
@@ -131,6 +140,21 @@
                 # Exception itself
                 h.flash(e, 'error')
 
+        # check if we use container plugin, and try to login using it.
+        auth_plugins = RhodeCodeSetting.get_auth_plugins()
+        if any((importplugin(name).is_container_auth for name in auth_plugins)):
+            from rhodecode.lib import auth_modules
+            try:
+                auth_info = auth_modules.authenticate('', '', request.environ)
+            except UserCreationError, e:
+                log.error(e)
+                h.flash(e, 'error')
+                # render login, with flash message about limit
+                return render('/login.html')
+
+            if auth_info:
+                headers = self._store_user_in_session(auth_info.get('username'))
+                raise HTTPFound(location=c.came_from, headers=headers)
         return render('/login.html')
 
     @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
@@ -139,14 +163,33 @@
         c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\
             .AuthUser.permissions['global']
 
+        settings = RhodeCodeSetting.get_app_settings()
+        captcha_private_key = settings.get('rhodecode_captcha_private_key')
+        c.captcha_active = bool(captcha_private_key)
+        c.captcha_public_key = settings.get('rhodecode_captcha_public_key')
+
         if request.POST:
             register_form = RegisterForm()()
             try:
                 form_result = register_form.to_python(dict(request.POST))
                 form_result['active'] = c.auto_active
+
+                if c.captcha_active:
+                    from rhodecode.lib.recaptcha import submit
+                    response = submit(request.POST.get('recaptcha_challenge_field'),
+                                      request.POST.get('recaptcha_response_field'),
+                                      private_key=captcha_private_key,
+                                      remoteip=self.ip_addr)
+                    if c.captcha_active and not response.is_valid:
+                        _value = form_result
+                        _msg = _('bad captcha')
+                        error_dict = {'recaptcha_field': _msg}
+                        raise formencode.Invalid(_msg, _value, None,
+                                                 error_dict=error_dict)
+
                 UserModel().create_registration(form_result)
                 h.flash(_('You have successfully registered into RhodeCode'),
-                            category='success')
+                        category='success')
                 Session().commit()
                 return redirect(url('login_home'))
 
@@ -167,10 +210,27 @@
         return render('/register.html')
 
     def password_reset(self):
+        settings = RhodeCodeSetting.get_app_settings()
+        captcha_private_key = settings.get('rhodecode_captcha_private_key')
+        c.captcha_active = bool(captcha_private_key)
+        c.captcha_public_key = settings.get('rhodecode_captcha_public_key')
+
         if request.POST:
             password_reset_form = PasswordResetForm()()
             try:
                 form_result = password_reset_form.to_python(dict(request.POST))
+                if c.captcha_active:
+                    from rhodecode.lib.recaptcha import submit
+                    response = submit(request.POST.get('recaptcha_challenge_field'),
+                                      request.POST.get('recaptcha_response_field'),
+                                      private_key=captcha_private_key,
+                                      remoteip=self.ip_addr)
+                    if c.captcha_active and not response.is_valid:
+                        _value = form_result
+                        _msg = _('bad captcha')
+                        error_dict = {'recaptcha_field': _msg}
+                        raise formencode.Invalid(_msg, _value, None,
+                                                 error_dict=error_dict)
                 UserModel().reset_password_link(form_result)
                 h.flash(_('Your password reset link was sent'),
                             category='success')
--- a/rhodecode/controllers/pullrequests.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/pullrequests.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.pullrequests
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    pull requests controller for rhodecode for initializing pull requests
-
-    :created_on: May 7, 2012
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.pullrequests
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+pull requests controller for rhodecode for initializing pull requests
+
+:created_on: May 7, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import logging
 import traceback
 import formencode
@@ -63,7 +64,7 @@
         super(PullrequestsController, self).__before__()
         repo_model = RepoModel()
         c.users_array = repo_model.get_users_js()
-        c.users_groups_array = repo_model.get_users_groups_js()
+        c.user_groups_array = repo_model.get_user_groups_js()
 
     def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
         """return a structure with repo's interesting changesets, suitable for
@@ -106,7 +107,8 @@
             if branch == abranch:
                 selected = n
                 branch = None
-        if branch: # branch not in list - it is probably closed
+
+        if branch:  # branch not in list - it is probably closed
             revs = repo._repo.revs('max(branch(%s))', branch)
             if revs:
                 cs = repo.get_changeset(revs[0])
@@ -124,7 +126,7 @@
         for tag, tagrev in repo.tags.iteritems():
             n = 'tag:%s:%s' % (tag, tagrev)
             tags.append((n, tag))
-            if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
+            if rev == tagrev and tag != 'tip':  # tip is not a real tag - and its branch is better
                 selected = n
 
         # prio 1: rev was selected as existing entry above
@@ -141,7 +143,14 @@
 
         # prio 4: tip revision
         if not selected:
-            selected = 'tag:tip:%s' % repo.tags['tip']
+            if h.is_hg(repo):
+                selected = 'tag:tip:%s' % repo.tags['tip']
+            else:
+                if 'master' in repo.branches:
+                    selected = 'branch:master:%s' % repo.branches['master']
+                else:
+                    k, v = repo.branches.items()[0]
+                    selected = 'branch:%s:%s' % (k, v)
 
         groups = [(specials, _("Special")),
                   (peers, _("Peer branches")),
@@ -155,7 +164,7 @@
         owner = self.rhodecode_user.user_id == pull_request.user_id
         reviewer = self.rhodecode_user.user_id in [x.user_id for x in
                                                    pull_request.reviewers]
-        return (self.rhodecode_user.admin or owner or reviewer)
+        return self.rhodecode_user.admin or owner or reviewer
 
     def _load_compare_data(self, pull_request, enable_comments=True):
         """
@@ -248,10 +257,6 @@
     def index(self):
         org_repo = c.rhodecode_db_repo
 
-        if org_repo.scm_instance.alias != 'hg':
-            log.error('Review not available for GIT REPOS')
-            raise HTTPNotFound
-
         try:
             org_repo.scm_instance.get_changeset()
         except EmptyRepositoryError, e:
@@ -366,9 +371,10 @@
             raise HTTPForbidden()
         #only owner or admin can update it
         owner = pull_request.author.user_id == c.rhodecode_user.user_id
-        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
+        repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
+        if h.HasPermissionAny('hg.admin') or repo_admin or owner:
             reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
-                       request.POST.get('reviewers_ids', '').split(',')))
+                request.POST.get('reviewers_ids', '').split(',')))
 
             PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
             Session().commit()
@@ -388,7 +394,7 @@
             Session().commit()
             h.flash(_('Successfully deleted pull request'),
                     category='success')
-            return redirect(url('admin_settings_my_account', anchor='pullrequests'))
+            return redirect(url('my_account_pullrequests'))
         raise HTTPForbidden()
 
     @LoginRequired()
@@ -397,7 +403,7 @@
     def show(self, repo_name, pull_request_id):
         repo_model = RepoModel()
         c.users_array = repo_model.get_users_js()
-        c.users_groups_array = repo_model.get_users_groups_js()
+        c.user_groups_array = repo_model.get_user_groups_js()
         c.pull_request = PullRequest.get_or_404(pull_request_id)
         c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
         cc_model = ChangesetCommentsModel()
@@ -544,7 +550,8 @@
             raise HTTPForbidden()
 
         owner = co.author.user_id == c.rhodecode_user.user_id
-        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
+        repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
+        if h.HasPermissionAny('hg.admin') or repo_admin or owner:
             ChangesetCommentsModel().delete(comment=co)
             Session().commit()
             return True
--- a/rhodecode/controllers/search.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/search.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.search
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Search controller for RhodeCode
-
-    :created_on: Aug 7, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.search
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Search controller for RhodeCode
+
+:created_on: Aug 7, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import logging
 import traceback
 import urllib
--- a/rhodecode/controllers/summary.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/summary.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.summary
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Summary controller for Rhodecode
-
-    :created_on: Apr 18, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.summary
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Summary controller for Rhodecode
+
+:created_on: Apr 18, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import traceback
 import calendar
@@ -37,12 +37,11 @@
 
 from beaker.cache import cache_region, region_invalidate
 
-from rhodecode.lib import helpers as h
 from rhodecode.lib.compat import product
 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
     NodeDoesNotExistError
 from rhodecode.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP
-from rhodecode.model.db import Statistics, CacheInvalidation
+from rhodecode.model.db import Statistics, CacheInvalidation, User
 from rhodecode.lib.utils import jsonify
 from rhodecode.lib.utils2 import safe_unicode, safe_str
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
@@ -52,8 +51,7 @@
 from rhodecode.lib.markup_renderer import MarkupRenderer
 from rhodecode.lib.celerylib import run_task
 from rhodecode.lib.celerylib.tasks import get_commits_stats
-from rhodecode.lib.helpers import RepoPage
-from rhodecode.lib.compat import json, OrderedDict
+from rhodecode.lib.compat import json
 from rhodecode.lib.vcs.nodes import FileNode
 from rhodecode.controllers.changelog import _load_changelog_summary
 
@@ -90,12 +88,12 @@
 
     def __get_readme_data(self, db_repo):
         repo_name = db_repo.repo_name
+        log.debug('Looking for README file')
 
         @cache_region('long_term')
         def _get_readme_from_cache(key, kind):
             readme_data = None
             readme_file = None
-            log.debug('Looking for README file')
             try:
                 # gets the landing revision! or tip if fails
                 cs = db_repo.get_landing_changeset()
@@ -110,7 +108,8 @@
                         readme_file = f
                         log.debug('Found README file `%s` rendering...' %
                                   readme_file)
-                        readme_data = renderer.render(readme.content, f)
+                        readme_data = renderer.render(readme.content,
+                                                      filename=f)
                         break
                     except NodeDoesNotExistError:
                         continue
@@ -134,40 +133,75 @@
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
     def index(self, repo_name):
-        c.dbrepo = dbrepo = c.rhodecode_db_repo
         _load_changelog_summary()
-        if self.rhodecode_user.username == 'default':
-            # for default(anonymous) user we don't need to pass credentials
-            username = ''
-            password = ''
+
+        username = ''
+        if self.rhodecode_user.username != User.DEFAULT_USER:
+            username = safe_str(self.rhodecode_user.username)
+
+        _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
+        if '{repo}' in _def_clone_uri:
+            _def_clone_uri_by_id = _def_clone_uri.replace('{repo}', '_{repoid}')
+        elif '{repoid}' in _def_clone_uri:
+            _def_clone_uri_by_id = _def_clone_uri.replace('_{repoid}', '{repo}')
+
+        c.clone_repo_url = c.rhodecode_db_repo.clone_url(user=username,
+                                                uri_tmpl=_def_clone_uri)
+        c.clone_repo_url_id = c.rhodecode_db_repo.clone_url(user=username,
+                                                uri_tmpl=_def_clone_uri_by_id)
+
+        if c.rhodecode_db_repo.enable_statistics:
+            c.show_stats = True
         else:
-            username = str(self.rhodecode_user.username)
-            password = '@'
+            c.show_stats = False
+
+        stats = self.sa.query(Statistics)\
+            .filter(Statistics.repository == c.rhodecode_db_repo)\
+            .scalar()
 
-        parsed_url = urlparse(url.current(qualified=True))
+        c.stats_percentage = 0
 
-        default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
+        if stats and stats.languages:
+            c.no_data = False is c.rhodecode_db_repo.enable_statistics
+            lang_stats_d = json.loads(stats.languages)
 
-        uri_tmpl = config.get('clone_uri', default_clone_uri)
-        uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
-        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
-        uri_dict = {
-           'user': urllib.quote(username),
-           'pass': password,
-           'scheme': parsed_url.scheme,
-           'netloc': parsed_url.netloc,
-           'path': urllib.quote(safe_str(decoded_path))
-        }
+            lang_stats = ((x, {"count": y,
+                               "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
+                          for x, y in lang_stats_d.items())
+
+            c.trending_languages = json.dumps(
+                sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
+            )
+        else:
+            c.no_data = True
+            c.trending_languages = json.dumps({})
+
+        c.enable_downloads = c.rhodecode_db_repo.enable_downloads
+        c.readme_data, c.readme_file = \
+            self.__get_readme_data(c.rhodecode_db_repo)
+        return render('summary/summary.html')
 
-        uri = (uri_tmpl % uri_dict)
-        # generate another clone url by id
-        uri_dict.update(
-         {'path': decoded_path.replace(repo_name, '_%s' % c.dbrepo.repo_id)}
-        )
-        uri_id = uri_tmpl % uri_dict
+    @LoginRequired()
+    @NotAnonymous()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    @jsonify
+    def repo_size(self, repo_name):
+        if request.is_xhr:
+            return c.rhodecode_db_repo._repo_size()
+        else:
+            raise HTTPBadRequest()
 
-        c.clone_repo_url = uri
-        c.clone_repo_url_id = uri_id
+    @LoginRequired()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    def statistics(self, repo_name):
+        if c.rhodecode_db_repo.enable_statistics:
+            c.show_stats = True
+            c.no_data_msg = _('No data loaded yet')
+        else:
+            c.show_stats = False
+            c.no_data_msg = _('Statistics are disabled for this repository')
 
         td = date.today() + timedelta(days=1)
         td_1m = td - timedelta(days=calendar.mdays[td.month])
@@ -176,27 +210,14 @@
         ts_min_m = mktime(td_1m.timetuple())
         ts_min_y = mktime(td_1y.timetuple())
         ts_max_y = mktime(td.timetuple())
-
-        if dbrepo.enable_statistics:
-            c.show_stats = True
-            c.no_data_msg = _('No data loaded yet')
-            recurse_limit = 500  # don't recurse more than 500 times when parsing
-            run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y,
-                     ts_max_y, recurse_limit)
-        else:
-            c.show_stats = False
-            c.no_data_msg = _('Statistics are disabled for this repository')
         c.ts_min = ts_min_m
         c.ts_max = ts_max_y
 
         stats = self.sa.query(Statistics)\
-            .filter(Statistics.repository == dbrepo)\
+            .filter(Statistics.repository == c.rhodecode_db_repo)\
             .scalar()
-
-        c.stats_percentage = 0
-
         if stats and stats.languages:
-            c.no_data = False is dbrepo.enable_statistics
+            c.no_data = False is c.rhodecode_db_repo.enable_statistics
             lang_stats_d = json.loads(stats.languages)
             c.commit_data = stats.commit_activity
             c.overview_data = stats.commit_activity_combined
@@ -222,21 +243,7 @@
             c.trending_languages = json.dumps({})
             c.no_data = True
 
-        c.enable_downloads = dbrepo.enable_downloads
-        if c.enable_downloads:
-            c.download_options = self._get_download_links(c.rhodecode_repo)
-
-        c.readme_data, c.readme_file = \
-            self.__get_readme_data(c.rhodecode_db_repo)
-        return render('summary/summary.html')
-
-    @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    @jsonify
-    def repo_size(self, repo_name):
-        if request.is_xhr:
-            return c.rhodecode_db_repo._repo_size()
-        else:
-            raise HTTPBadRequest()
+        recurse_limit = 500  # don't recurse more than 500 times when parsing
+        run_task(get_commits_stats, c.rhodecode_db_repo.repo_name, ts_min_y,
+                 ts_max_y, recurse_limit)
+        return render('summary/statistics.html')
--- a/rhodecode/controllers/tags.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/controllers/tags.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.tags
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Tags controller for rhodecode
-
-    :created_on: Apr 21, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,19 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.controllers.tags
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Tags controller for rhodecode
+
+:created_on: Apr 21, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+"""
+
 import logging
 
 from pylons import tmpl_context as c
--- a/rhodecode/lib/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,6 +1,30 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib
+~~~~~~~~~~~~~
+
+RhodeCode libs
+
+:created_on: Oct 06, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import os
 
-
 def get_current_revision(quiet=False):
     """
     Returns tuple of (number, id) from repository containing this package
--- a/rhodecode/lib/annotate.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/annotate.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,28 @@
 # -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
-    rhodecode.lib.annotate
-    ~~~~~~~~~~~~~~~~~~~~~~
+rhodecode.lib.annotate
+~~~~~~~~~~~~~~~~~~~~~~
 
-    Anontation library for usage in rhodecode, previously part of vcs
+Anontation library for usage in rhodecode, previously part of vcs
 
-    :created_on: Dec 4, 2011
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
+:created_on: Dec 4, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
 """
+
 import StringIO
 
 from rhodecode.lib.vcs.exceptions import VCSError
--- a/rhodecode/lib/app_globals.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/app_globals.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,13 +1,37 @@
-"""The application's Globals object"""
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+rhodecode.lib.app_globals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The application's Globals object
+
+:created_on: Oct 06, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 from beaker.cache import CacheManager
 from beaker.util import parse_cache_config_options
 
 
 class Globals(object):
-    """Globals acts as a container for objects available throughout the
+    """
+    Globals acts as a container for objects available throughout the
     life of the application
-
     """
 
     def __init__(self, config):
--- a/rhodecode/lib/auth.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/auth.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.auth
-    ~~~~~~~~~~~~~~~~~~
-
-    authentication and permission libraries
-
-    :created_on: Apr 4, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,34 +11,52 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.auth
+~~~~~~~~~~~~~~~~~~
 
+authentication and permission libraries
+
+:created_on: Apr 4, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+from __future__ import with_statement
+import time
 import random
 import logging
 import traceback
 import hashlib
+import itertools
+import collections
 
 from tempfile import _RandomNameSequence
 from decorator import decorator
 
-from pylons import config, url, request
+from pylons import url, request
 from pylons.controllers.util import abort, redirect
 from pylons.i18n.translation import _
+from sqlalchemy import or_
 from sqlalchemy.orm.exc import ObjectDeletedError
+from sqlalchemy.orm import joinedload
 
 from rhodecode import __platform__, is_windows, is_unix
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.model import meta
 from rhodecode.model.meta import Session
+from rhodecode.model.user import UserModel
+from rhodecode.model.db import User, Repository, Permission, \
+    UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \
+    RepoGroup, UserGroupRepoGroupToPerm, UserIpMap, UserGroupUserGroupToPerm, \
+    UserGroup, UserApiKeys
 
-from rhodecode.lib.utils2 import str2bool, safe_unicode, aslist
-from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError,\
-    LdapImportError
-from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug,\
-    get_user_group_slug
-from rhodecode.lib.auth_ldap import AuthLdap
+from rhodecode.lib.utils2 import safe_unicode, aslist
+from rhodecode.lib.utils import get_repo_slug, get_repo_group_slug, \
+    get_user_group_slug, conditional_cache
+from rhodecode.lib.caching_query import FromCache
 
-from rhodecode.model import meta
-from rhodecode.model.user import UserModel
-from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
-from rhodecode.lib.caching_query import FromCache
+from beaker.cache import cache_region
 
 log = logging.getLogger(__name__)
 
@@ -149,166 +156,6 @@
     return hashlib.sha1(str_ + salt).hexdigest()
 
 
-def authfunc(environ, username, password):
-    """
-    Dummy authentication wrapper function used in Mercurial and Git for
-    access control.
-
-    :param environ: needed only for using in Basic auth
-    """
-    return authenticate(username, password)
-
-
-def authenticate(username, password):
-    """
-    Authentication function used for access control,
-    firstly checks for db authentication then if ldap is enabled for ldap
-    authentication, also creates ldap user if not in database
-
-    :param username: username
-    :param password: password
-    """
-
-    user_model = UserModel()
-    user = User.get_by_username(username)
-
-    log.debug('Authenticating user using RhodeCode account')
-    if user is not None and not user.ldap_dn:
-        if user.active:
-            if user.username == 'default' and user.active:
-                log.info('user %s authenticated correctly as anonymous user' %
-                         username)
-                return True
-
-            elif user.username == username and check_password(password,
-                                                              user.password):
-                log.info('user %s authenticated correctly' % username)
-                return True
-        else:
-            log.warning('user %s tried auth but is disabled' % username)
-
-    else:
-        log.debug('Regular authentication failed')
-        user_obj = User.get_by_username(username, case_insensitive=True)
-
-        if user_obj is not None and not user_obj.ldap_dn:
-            log.debug('this user already exists as non ldap')
-            return False
-
-        ldap_settings = RhodeCodeSetting.get_ldap_settings()
-        #======================================================================
-        # FALLBACK TO LDAP AUTH IF ENABLE
-        #======================================================================
-        if str2bool(ldap_settings.get('ldap_active')):
-            log.debug("Authenticating user using ldap")
-            kwargs = {
-                  'server': ldap_settings.get('ldap_host', ''),
-                  'base_dn': ldap_settings.get('ldap_base_dn', ''),
-                  'port': ldap_settings.get('ldap_port'),
-                  'bind_dn': ldap_settings.get('ldap_dn_user'),
-                  'bind_pass': ldap_settings.get('ldap_dn_pass'),
-                  'tls_kind': ldap_settings.get('ldap_tls_kind'),
-                  'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
-                  'ldap_filter': ldap_settings.get('ldap_filter'),
-                  'search_scope': ldap_settings.get('ldap_search_scope'),
-                  'attr_login': ldap_settings.get('ldap_attr_login'),
-                  'ldap_version': 3,
-                  }
-            log.debug('Checking for ldap authentication')
-            try:
-                aldap = AuthLdap(**kwargs)
-                (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
-                                                                password)
-                log.debug('Got ldap DN response %s' % user_dn)
-
-                get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
-                                                           .get(k), [''])[0]
-
-                user_attrs = {
-                 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
-                 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
-                 'email': get_ldap_attr('ldap_attr_email'),
-                 'active': 'hg.extern_activate.auto' in User.get_default_user()\
-                                                .AuthUser.permissions['global']
-                }
-
-                # don't store LDAP password since we don't need it. Override
-                # with some random generated password
-                _password = PasswordGenerator().gen_password(length=8)
-                # create this user on the fly if it doesn't exist in rhodecode
-                # database
-                if user_model.create_ldap(username, _password, user_dn,
-                                          user_attrs):
-                    log.info('created new ldap user %s' % username)
-
-                Session().commit()
-                return True
-            except (LdapUsernameError, LdapPasswordError, LdapImportError):
-                pass
-            except (Exception,):
-                log.error(traceback.format_exc())
-                pass
-    return False
-
-
-def login_container_auth(username):
-    user = User.get_by_username(username)
-    if user is None:
-        user_attrs = {
-            'name': username,
-            'lastname': None,
-            'email': None,
-            'active': 'hg.extern_activate.auto' in User.get_default_user()\
-                                            .AuthUser.permissions['global']
-        }
-        user = UserModel().create_for_container_auth(username, user_attrs)
-        if not user:
-            return None
-        log.info('User %s was created by container authentication' % username)
-
-    if not user.active:
-        return None
-
-    user.update_lastlogin()
-    Session().commit()
-
-    log.debug('User %s is now logged in by container authentication',
-              user.username)
-    return user
-
-
-def get_container_username(environ, config, clean_username=False):
-    """
-    Gets the container_auth username (or email). It tries to get username
-    from REMOTE_USER if container_auth_enabled is enabled, if that fails
-    it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled
-    is enabled. clean_username extracts the username from this data if it's
-    having @ in it.
-
-    :param environ:
-    :param config:
-    :param clean_username:
-    """
-    username = None
-
-    if str2bool(config.get('container_auth_enabled', False)):
-        from paste.httpheaders import REMOTE_USER
-        username = REMOTE_USER(environ)
-        log.debug('extracted REMOTE_USER:%s' % (username))
-
-    if not username and str2bool(config.get('proxypass_auth_enabled', False)):
-        username = environ.get('HTTP_X_FORWARDED_USER')
-        log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username))
-
-    if username and clean_username:
-        # Removing realm and domain from username
-        username = username.partition('@')[0]
-        username = username.rpartition('\\')[2]
-    log.debug('Received username %s from container' % username)
-
-    return username
-
-
 class CookieStoreWrapper(object):
 
     def __init__(self, cookie_store):
@@ -324,60 +171,361 @@
             return self.cookie_store.__dict__.get(key, other)
 
 
-class  AuthUser(object):
+
+def _cached_perms_data(user_id, user_is_admin, user_inherit_default_permissions,
+                       explicit, algo):
+    RK = 'repositories'
+    GK = 'repositories_groups'
+    UK = 'user_groups'
+    GLOBAL = 'global'
+    PERM_WEIGHTS = Permission.PERM_WEIGHTS
+    permissions = {RK: {}, GK: {}, UK: {}, GLOBAL: set()}
+
+    def _choose_perm(new_perm, cur_perm):
+        new_perm_val = PERM_WEIGHTS[new_perm]
+        cur_perm_val = PERM_WEIGHTS[cur_perm]
+        if algo == 'higherwin':
+            if new_perm_val > cur_perm_val:
+                return new_perm
+            return cur_perm
+        elif algo == 'lowerwin':
+            if new_perm_val < cur_perm_val:
+                return new_perm
+            return cur_perm
+
+    #======================================================================
+    # fetch default permissions
+    #======================================================================
+    default_user = User.get_by_username('default', cache=True)
+    default_user_id = default_user.user_id
+
+    default_repo_perms = Permission.get_default_perms(default_user_id)
+    default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
+    default_user_group_perms = Permission.get_default_user_group_perms(default_user_id)
+
+    if user_is_admin:
+        #==================================================================
+        # admin user have all default rights for repositories
+        # and groups set to admin
+        #==================================================================
+        permissions[GLOBAL].add('hg.admin')
+        permissions[GLOBAL].add('hg.create.write_on_repogroup.true')
+
+        # repositories
+        for perm in default_repo_perms:
+            r_k = perm.UserRepoToPerm.repository.repo_name
+            p = 'repository.admin'
+            permissions[RK][r_k] = p
+
+        # repository groups
+        for perm in default_repo_groups_perms:
+            rg_k = perm.UserRepoGroupToPerm.group.group_name
+            p = 'group.admin'
+            permissions[GK][rg_k] = p
+
+        # user groups
+        for perm in default_user_group_perms:
+            u_k = perm.UserUserGroupToPerm.user_group.users_group_name
+            p = 'usergroup.admin'
+            permissions[UK][u_k] = p
+        return permissions
+
+    #==================================================================
+    # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS
+    #==================================================================
+    uid = user_id
+
+    # default global permissions taken fron the default user
+    default_global_perms = UserToPerm.query()\
+        .filter(UserToPerm.user_id == default_user_id)\
+        .options(joinedload(UserToPerm.permission))
+
+    for perm in default_global_perms:
+        permissions[GLOBAL].add(perm.permission.permission_name)
+
+    # defaults for repositories, taken from default user
+    for perm in default_repo_perms:
+        r_k = perm.UserRepoToPerm.repository.repo_name
+        if perm.Repository.private and not (perm.Repository.user_id == uid):
+            # disable defaults for private repos,
+            p = 'repository.none'
+        elif perm.Repository.user_id == uid:
+            # set admin if owner
+            p = 'repository.admin'
+        else:
+            p = perm.Permission.permission_name
+
+        permissions[RK][r_k] = p
+
+    # defaults for repository groups taken from default user permission
+    # on given group
+    for perm in default_repo_groups_perms:
+        rg_k = perm.UserRepoGroupToPerm.group.group_name
+        p = perm.Permission.permission_name
+        permissions[GK][rg_k] = p
+
+    # defaults for user groups taken from default user permission
+    # on given user group
+    for perm in default_user_group_perms:
+        u_k = perm.UserUserGroupToPerm.user_group.users_group_name
+        p = perm.Permission.permission_name
+        permissions[UK][u_k] = p
+
+    #======================================================================
+    # !! OVERRIDE GLOBALS !! with user permissions if any found
+    #======================================================================
+    # those can be configured from groups or users explicitly
+    _configurable = set([
+        'hg.fork.none', 'hg.fork.repository',
+        'hg.create.none', 'hg.create.repository',
+        'hg.usergroup.create.false', 'hg.usergroup.create.true'
+    ])
+
+    # USER GROUPS comes first
+    # user group global permissions
+    user_perms_from_users_groups = Session().query(UserGroupToPerm)\
+        .options(joinedload(UserGroupToPerm.permission))\
+        .join((UserGroupMember, UserGroupToPerm.users_group_id ==
+               UserGroupMember.users_group_id))\
+        .filter(UserGroupMember.user_id == uid)\
+        .order_by(UserGroupToPerm.users_group_id)\
+        .all()
+    # need to group here by groups since user can be in more than
+    # one group
+    _grouped = [[x, list(y)] for x, y in
+                itertools.groupby(user_perms_from_users_groups,
+                                  lambda x:x.users_group)]
+    for gr, perms in _grouped:
+        # since user can be in multiple groups iterate over them and
+        # select the lowest permissions first (more explicit)
+        ##TODO: do this^^
+        if not gr.inherit_default_permissions:
+            # NEED TO IGNORE all configurable permissions and
+            # replace them with explicitly set
+            permissions[GLOBAL] = permissions[GLOBAL]\
+                                            .difference(_configurable)
+        for perm in perms:
+            permissions[GLOBAL].add(perm.permission.permission_name)
+
+    # user specific global permissions
+    user_perms = Session().query(UserToPerm)\
+            .options(joinedload(UserToPerm.permission))\
+            .filter(UserToPerm.user_id == uid).all()
+
+    if not user_inherit_default_permissions:
+        # NEED TO IGNORE all configurable permissions and
+        # replace them with explicitly set
+        permissions[GLOBAL] = permissions[GLOBAL]\
+                                        .difference(_configurable)
+
+        for perm in user_perms:
+            permissions[GLOBAL].add(perm.permission.permission_name)
+    ## END GLOBAL PERMISSIONS
+
+    #======================================================================
+    # !! PERMISSIONS FOR REPOSITORIES !!
+    #======================================================================
+    #======================================================================
+    # check if user is part of user groups for this repository and
+    # fill in his permission from it. _choose_perm decides of which
+    # permission should be selected based on selected method
+    #======================================================================
+
+    # user group for repositories permissions
+    user_repo_perms_from_users_groups = \
+     Session().query(UserGroupRepoToPerm, Permission, Repository,)\
+        .join((Repository, UserGroupRepoToPerm.repository_id ==
+               Repository.repo_id))\
+        .join((Permission, UserGroupRepoToPerm.permission_id ==
+               Permission.permission_id))\
+        .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
+               UserGroupMember.users_group_id))\
+        .filter(UserGroupMember.user_id == uid)\
+        .all()
+
+    multiple_counter = collections.defaultdict(int)
+    for perm in user_repo_perms_from_users_groups:
+        r_k = perm.UserGroupRepoToPerm.repository.repo_name
+        multiple_counter[r_k] += 1
+        p = perm.Permission.permission_name
+        cur_perm = permissions[RK][r_k]
+
+        if perm.Repository.user_id == uid:
+            # set admin if owner
+            p = 'repository.admin'
+        else:
+            if multiple_counter[r_k] > 1:
+                p = _choose_perm(p, cur_perm)
+        permissions[RK][r_k] = p
+
+    # user explicit permissions for repositories, overrides any specified
+    # by the group permission
+    user_repo_perms = Permission.get_default_perms(uid)
+    for perm in user_repo_perms:
+        r_k = perm.UserRepoToPerm.repository.repo_name
+        cur_perm = permissions[RK][r_k]
+        # set admin if owner
+        if perm.Repository.user_id == uid:
+            p = 'repository.admin'
+        else:
+            p = perm.Permission.permission_name
+            if not explicit:
+                p = _choose_perm(p, cur_perm)
+        permissions[RK][r_k] = p
+
+    #======================================================================
+    # !! PERMISSIONS FOR REPOSITORY GROUPS !!
+    #======================================================================
+    #======================================================================
+    # check if user is part of user groups for this repository groups and
+    # fill in his permission from it. _choose_perm decides of which
+    # permission should be selected based on selected method
+    #======================================================================
+    # user group for repo groups permissions
+    user_repo_group_perms_from_users_groups = \
+     Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\
+     .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
+     .join((Permission, UserGroupRepoGroupToPerm.permission_id
+            == Permission.permission_id))\
+     .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
+            == UserGroupMember.users_group_id))\
+     .filter(UserGroupMember.user_id == uid)\
+     .all()
+
+    multiple_counter = collections.defaultdict(int)
+    for perm in user_repo_group_perms_from_users_groups:
+        g_k = perm.UserGroupRepoGroupToPerm.group.group_name
+        multiple_counter[g_k] += 1
+        p = perm.Permission.permission_name
+        cur_perm = permissions[GK][g_k]
+        if multiple_counter[g_k] > 1:
+            p = _choose_perm(p, cur_perm)
+        permissions[GK][g_k] = p
+
+    # user explicit permissions for repository groups
+    user_repo_groups_perms = Permission.get_default_group_perms(uid)
+    for perm in user_repo_groups_perms:
+        rg_k = perm.UserRepoGroupToPerm.group.group_name
+        p = perm.Permission.permission_name
+        cur_perm = permissions[GK][rg_k]
+        if not explicit:
+            p = _choose_perm(p, cur_perm)
+        permissions[GK][rg_k] = p
+
+    #======================================================================
+    # !! PERMISSIONS FOR USER GROUPS !!
+    #======================================================================
+    # user group for user group permissions
+    user_group_user_groups_perms = \
+     Session().query(UserGroupUserGroupToPerm, Permission, UserGroup)\
+     .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id
+            == UserGroup.users_group_id))\
+     .join((Permission, UserGroupUserGroupToPerm.permission_id
+            == Permission.permission_id))\
+     .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id
+            == UserGroupMember.users_group_id))\
+     .filter(UserGroupMember.user_id == uid)\
+     .all()
+
+    multiple_counter = collections.defaultdict(int)
+    for perm in user_group_user_groups_perms:
+        g_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
+        multiple_counter[g_k] += 1
+        p = perm.Permission.permission_name
+        cur_perm = permissions[UK][g_k]
+        if multiple_counter[g_k] > 1:
+            p = _choose_perm(p, cur_perm)
+        permissions[UK][g_k] = p
+
+    #user explicit permission for user groups
+    user_user_groups_perms = Permission.get_default_user_group_perms(uid)
+    for perm in user_user_groups_perms:
+        u_k = perm.UserUserGroupToPerm.user_group.users_group_name
+        p = perm.Permission.permission_name
+        cur_perm = permissions[UK][u_k]
+        if not explicit:
+            p = _choose_perm(p, cur_perm)
+        permissions[UK][u_k] = p
+
+    return permissions
+
+
+def allowed_api_access(controller_name, whitelist=None, api_key=None):
+    """
+    Check if given controller_name is in whitelist API access
+    """
+    if not whitelist:
+        from rhodecode import CONFIG
+        whitelist = aslist(CONFIG.get('api_access_controllers_whitelist'),
+                           sep=',')
+        log.debug('whitelist of API access is: %s' % (whitelist))
+    api_access_valid = controller_name in whitelist
+    if api_access_valid:
+        log.debug('controller:%s is in API whitelist' % (controller_name))
+    else:
+        msg = 'controller: %s is *NOT* in API whitelist' % (controller_name)
+        if api_key:
+            #if we use API key and don't have access it's a warning
+            log.warning(msg)
+        else:
+            log.debug(msg)
+    return api_access_valid
+
+
+class AuthUser(object):
     """
     A simple object that handles all attributes of user in RhodeCode
 
     It does lookup based on API key,given user, or user present in session
     Then it fills all required information for such user. It also checks if
-    anonymous access is enabled and if so, it returns default user as logged
-    in
+    anonymous access is enabled and if so, it returns default user as logged in
     """
 
     def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
 
         self.user_id = user_id
+        self._api_key = api_key
+
         self.api_key = None
         self.username = username
         self.ip_addr = ip_addr
-
         self.name = ''
         self.lastname = ''
         self.email = ''
         self.is_authenticated = False
         self.admin = False
         self.inherit_default_permissions = False
-        self.permissions = {}
-        self._api_key = api_key
+
         self.propagate_data()
         self._instance = None
 
+    @LazyProperty
+    def permissions(self):
+        return self.get_perms(user=self, cache=False)
+
+    @property
+    def api_keys(self):
+        return self.get_api_keys()
+
     def propagate_data(self):
         user_model = UserModel()
-        self.anonymous_user = User.get_by_username('default', cache=True)
+        self.anonymous_user = User.get_default_user(cache=True)
         is_user_loaded = False
 
-        # try go get user by api key
-        if self._api_key and self._api_key != self.anonymous_user.api_key:
-            log.debug('Auth User lookup by API KEY %s' % self._api_key)
-            is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
         # lookup by userid
-        elif (self.user_id is not None and
-              self.user_id != self.anonymous_user.user_id):
+        if self.user_id is not None and self.user_id != self.anonymous_user.user_id:
             log.debug('Auth User lookup by USER ID %s' % self.user_id)
             is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
+
+        # try go get user by api key
+        elif self._api_key and self._api_key != self.anonymous_user.api_key:
+            log.debug('Auth User lookup by API KEY %s' % self._api_key)
+            is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
+
         # lookup by username
-        elif self.username and \
-            str2bool(config.get('container_auth_enabled', False)):
-
+        elif self.username:
             log.debug('Auth User lookup by USER NAME %s' % self.username)
-            dbuser = login_container_auth(self.username)
-            if dbuser is not None:
-                log.debug('filling all attributes to object')
-                for k, v in dbuser.get_dict().items():
-                    setattr(self, k, v)
-                self.set_authenticated()
-                is_user_loaded = True
+            is_user_loaded = user_model.fill_data(self, username=self.username)
         else:
             log.debug('No data in %s that could been used to log in' % self)
 
@@ -396,7 +544,42 @@
             self.username = 'None'
 
         log.debug('Auth User is now %s' % self)
-        user_model.fill_perms(self)
+
+    def get_perms(self, user, explicit=True, algo='higherwin', cache=False):
+        """
+        Fills user permission attribute with permissions taken from database
+        works for permissions given for repositories, and for permissions that
+        are granted to groups
+
+        :param user: instance of User object from database
+        :param explicit: In case there are permissions both for user and a group
+            that user is part of, explicit flag will defiine if user will
+            explicitly override permissions from group, if it's False it will
+            make decision based on the algo
+        :param algo: algorithm to decide what permission should be choose if
+            it's multiple defined, eg user in two different groups. It also
+            decides if explicit flag is turned off how to specify the permission
+            for case when user is in a group + have defined separate permission
+        """
+        user_id = user.user_id
+        user_is_admin = user.is_admin
+        user_inherit_default_permissions = user.inherit_default_permissions
+
+        log.debug('Getting PERMISSION tree')
+        compute = conditional_cache('short_term', 'cache_desc',
+                                    condition=cache, func=_cached_perms_data)
+        return compute(user_id, user_is_admin,
+                       user_inherit_default_permissions, explicit, algo)
+
+    def get_api_keys(self):
+        api_keys = [self.api_key]
+        for api_key in UserApiKeys.query()\
+                .filter(UserApiKeys.user_id == self.user_id)\
+                .filter(or_(UserApiKeys.expires == -1,
+                            UserApiKeys.expires >= time.time())).all():
+            api_keys.append(api_key.api_key)
+
+        return api_keys
 
     @property
     def is_admin(self):
@@ -434,14 +617,21 @@
 
         :returns: boolean, True if ip is in allowed ip range
         """
-        #check IP
-        allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
-        if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
-            log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
+        # check IP
+        inherit = self.inherit_default_permissions
+        return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
+                                         inherit_from_default=inherit)
+
+    @classmethod
+    def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
+        allowed_ips = AuthUser.get_allowed_ips(user_id, cache=True,
+                        inherit_from_default=inherit_from_default)
+        if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
+            log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
             return True
         else:
             log.info('Access for IP:%s forbidden, '
-                     'not in %s' % (self.ip_addr, allowed_ips))
+                     'not in %s' % (ip_addr, allowed_ips))
             return False
 
     def __repr__(self):
@@ -471,12 +661,30 @@
         return AuthUser(user_id, api_key, username)
 
     @classmethod
-    def get_allowed_ips(cls, user_id, cache=False):
+    def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
         _set = set()
+
+        if inherit_from_default:
+            default_ips = UserIpMap.query().filter(UserIpMap.user ==
+                                            User.get_default_user(cache=True))
+            if cache:
+                default_ips = default_ips.options(FromCache("sql_cache_short",
+                                                  "get_user_ips_default"))
+
+            # populate from default user
+            for ip in default_ips:
+                try:
+                    _set.add(ip.ip_addr)
+                except ObjectDeletedError:
+                    # since we use heavy caching sometimes it happens that we get
+                    # deleted objects here, we just skip them
+                    pass
+
         user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
         if cache:
             user_ips = user_ips.options(FromCache("sql_cache_short",
                                                   "get_user_ips_%s" % user_id))
+
         for ip in user_ips:
             try:
                 _set.add(ip.ip_addr)
@@ -530,38 +738,45 @@
         cls = fargs[0]
         user = cls.rhodecode_user
         loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
-        # defined whitelist of controllers which API access will be enabled
-        whitelist = aslist(config.get('api_access_controllers_whitelist'),
-                           sep=',')
-        api_access_whitelist = loc in whitelist
-        log.debug('loc:%s is in API whitelist:%s:%s' % (loc, whitelist,
-                                                        api_access_whitelist))
-        #check IP
-        ip_access_ok = True
+
+        # check if our IP is allowed
+        ip_access_valid = True
         if not user.ip_allowed:
             from rhodecode.lib import helpers as h
             h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
                     category='warning')
-            ip_access_ok = False
+            ip_access_valid = False
 
-        api_access_ok = False
-        if self.api_access or api_access_whitelist:
+        # check if we used an APIKEY and it's a valid one
+        # defined whitelist of controllers which API access will be enabled
+        _api_key = request.GET.get('api_key', '')
+        api_access_valid = allowed_api_access(loc, api_key=_api_key)
+
+        # explicit controller is enabled or API is in our whitelist
+        if self.api_access or api_access_valid:
             log.debug('Checking API KEY access for %s' % cls)
-            if user.api_key == request.GET.get('api_key'):
-                api_access_ok = True
+            if _api_key and _api_key in user.api_keys:
+                api_access_valid = True
+                log.debug('API KEY ****%s is VALID' % _api_key[-4:])
             else:
-                log.debug("API KEY token not valid")
+                api_access_valid = False
+                if not _api_key:
+                    log.debug("API KEY *NOT* present in request")
+                else:
+                    log.warn("API KEY ****%s *NOT* valid" % _api_key[-4:])
 
         log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
-        if (user.is_authenticated or api_access_ok) and ip_access_ok:
-            reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
-            log.info('user %s is authenticated and granted access to %s '
-                     'using %s' % (user.username, loc, reason)
+        reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
+
+        if ip_access_valid and (user.is_authenticated or api_access_valid):
+            log.info('user %s authenticating with:%s IS authenticated on func %s '
+                     % (user, reason, loc)
             )
             return func(*fargs, **fkwargs)
         else:
-            log.warn('user %s NOT authenticated on func: %s' % (
-                user, loc)
+            log.warn('user %s authenticating with:%s NOT authenticated on func: %s: '
+                     'IP_ACCESS:%s API_ACCESS:%s'
+                     % (user, reason, loc, ip_access_valid, api_access_valid)
             )
             p = url.current()
 
@@ -583,7 +798,7 @@
 
         log.debug('Checking if user is not anonymous @%s' % cls)
 
-        anonymous = self.user.username == 'default'
+        anonymous = self.user.username == User.DEFAULT_USER
 
         if anonymous:
             p = url.current()
@@ -601,10 +816,6 @@
     """Base class for controller decorators"""
 
     def __init__(self, *required_perms):
-        available_perms = config['available_permissions']
-        for perm in required_perms:
-            if perm not in available_perms:
-                raise Exception("'%s' permission is not defined" % perm)
         self.required_perms = set(required_perms)
         self.user_perms = None
 
@@ -624,7 +835,7 @@
 
         else:
             log.debug('Permission denied for %s %s' % (cls, self.user))
-            anonymous = self.user.username == 'default'
+            anonymous = self.user.username == User.DEFAULT_USER
 
             if anonymous:
                 p = url.current()
@@ -703,14 +914,14 @@
         return False
 
 
-class HasReposGroupPermissionAllDecorator(PermsDecorator):
+class HasRepoGroupPermissionAllDecorator(PermsDecorator):
     """
     Checks for access permission for all given predicates for specific
     repository group. All of them have to be meet in order to fulfill the request
     """
 
     def check_permissions(self):
-        group_name = get_repos_group_slug(request)
+        group_name = get_repo_group_slug(request)
         try:
             user_perms = set([self.user_perms['repositories_groups'][group_name]])
         except KeyError:
@@ -721,14 +932,14 @@
         return False
 
 
-class HasReposGroupPermissionAnyDecorator(PermsDecorator):
+class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
     """
     Checks for access permission for any of given predicates for specific
     repository group. In order to fulfill the request any of predicates must be meet
     """
 
     def check_permissions(self):
-        group_name = get_repos_group_slug(request)
+        group_name = get_repo_group_slug(request)
         try:
             user_perms = set([self.user_perms['repositories_groups'][group_name]])
         except KeyError:
@@ -782,27 +993,28 @@
     """Base function for other check functions"""
 
     def __init__(self, *perms):
-        available_perms = config['available_permissions']
-
-        for perm in perms:
-            if perm not in available_perms:
-                raise Exception("'%s' permission is not defined" % perm)
         self.required_perms = set(perms)
         self.user_perms = None
         self.repo_name = None
         self.group_name = None
 
-    def __call__(self, check_location=''):
-        #TODO: put user as attribute here
-        user = request.user
+    def __call__(self, check_location='', user=None):
+        if not user:
+            #TODO: remove this someday,put as user as attribute here
+            user = request.user
+
+        # init auth user if not already given
+        if not isinstance(user, AuthUser):
+            user = AuthUser(user.user_id)
+
         cls_name = self.__class__.__name__
         check_scope = {
             'HasPermissionAll': '',
             'HasPermissionAny': '',
             'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
             'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
-            'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
-            'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
+            'HasRepoGroupPermissionAll': 'group:%s' % self.group_name,
+            'HasRepoGroupPermissionAny': 'group:%s' % self.group_name,
         }.get(cls_name, '?')
         log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
                   self.required_perms, user, check_scope,
@@ -812,13 +1024,15 @@
             return False
         self.user_perms = user.permissions
         if self.check_permissions():
-            log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
-                      check_location or 'unspecified location')
+            log.debug('Permission to %s granted for user: %s @ %s'
+                      % (check_scope, user,
+                         check_location or 'unspecified location'))
             return True
 
         else:
-            log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
-                        check_location or 'unspecified location')
+            log.debug('Permission to %s denied for user: %s @ %s'
+                      % (check_scope, user,
+                         check_location or 'unspecified location'))
             return False
 
     def check_permissions(self):
@@ -841,9 +1055,9 @@
 
 
 class HasRepoPermissionAll(PermsFunction):
-    def __call__(self, repo_name=None, check_location=''):
+    def __call__(self, repo_name=None, check_location='', user=None):
         self.repo_name = repo_name
-        return super(HasRepoPermissionAll, self).__call__(check_location)
+        return super(HasRepoPermissionAll, self).__call__(check_location, user)
 
     def check_permissions(self):
         if not self.repo_name:
@@ -861,9 +1075,9 @@
 
 
 class HasRepoPermissionAny(PermsFunction):
-    def __call__(self, repo_name=None, check_location=''):
+    def __call__(self, repo_name=None, check_location='', user=None):
         self.repo_name = repo_name
-        return super(HasRepoPermissionAny, self).__call__(check_location)
+        return super(HasRepoPermissionAny, self).__call__(check_location, user)
 
     def check_permissions(self):
         if not self.repo_name:
@@ -880,10 +1094,10 @@
         return False
 
 
-class HasReposGroupPermissionAny(PermsFunction):
-    def __call__(self, group_name=None, check_location=''):
+class HasRepoGroupPermissionAny(PermsFunction):
+    def __call__(self, group_name=None, check_location='', user=None):
         self.group_name = group_name
-        return super(HasReposGroupPermissionAny, self).__call__(check_location)
+        return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
 
     def check_permissions(self):
         try:
@@ -897,10 +1111,10 @@
         return False
 
 
-class HasReposGroupPermissionAll(PermsFunction):
-    def __call__(self, group_name=None, check_location=''):
+class HasRepoGroupPermissionAll(PermsFunction):
+    def __call__(self, group_name=None, check_location='', user=None):
         self.group_name = group_name
-        return super(HasReposGroupPermissionAll, self).__call__(check_location)
+        return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
 
     def check_permissions(self):
         try:
@@ -915,9 +1129,9 @@
 
 
 class HasUserGroupPermissionAny(PermsFunction):
-    def __call__(self, user_group_name=None, check_location=''):
+    def __call__(self, user_group_name=None, check_location='', user=None):
         self.user_group_name = user_group_name
-        return super(HasUserGroupPermissionAny, self).__call__(check_location)
+        return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
 
     def check_permissions(self):
         try:
@@ -932,9 +1146,9 @@
 
 
 class HasUserGroupPermissionAll(PermsFunction):
-    def __call__(self, user_group_name=None, check_location=''):
+    def __call__(self, user_group_name=None, check_location='', user=None):
         self.user_group_name = user_group_name
-        return super(HasUserGroupPermissionAll, self).__call__(check_location)
+        return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
 
     def check_permissions(self):
         try:
@@ -947,6 +1161,7 @@
             return True
         return False
 
+
 #==============================================================================
 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
 #==============================================================================
@@ -974,15 +1189,11 @@
                   'permissions %s for user:%s repository:%s', self.user_perms,
                                                 self.username, self.repo_name)
         if self.required_perms.intersection(self.user_perms):
-            log.debug('permission granted for user:%s on repo:%s' % (
-                          self.username, self.repo_name
-                     )
-            )
+            log.debug('Permission to repo: %s granted for user: %s @ %s'
+                      % (self.repo_name, self.username, 'PermissionMiddleware'))
             return True
-        log.debug('permission denied for user:%s on repo:%s' % (
-                      self.username, self.repo_name
-                 )
-        )
+        log.debug('Permission to repo: %s denied for user: %s @ %s'
+                  % (self.repo_name, self.username, 'PermissionMiddleware'))
         return False
 
 
@@ -993,11 +1204,18 @@
     def __init__(self, *perms):
         self.required_perms = set(perms)
 
-    def __call__(self, check_location='unspecified', user=None, repo_name=None):
+    def __call__(self, check_location=None, user=None, repo_name=None,
+                 group_name=None):
         cls_name = self.__class__.__name__
-        check_scope = 'user:%s, repo:%s' % (user, repo_name)
-        log.debug('checking cls:%s %s %s @ %s', cls_name,
-                  self.required_perms, check_scope, check_location)
+        check_scope = 'user:%s' % (user)
+        if repo_name:
+            check_scope += ', repo:%s' % (repo_name)
+
+        if group_name:
+            check_scope += ', repo group:%s' % (group_name)
+
+        log.debug('checking cls:%s %s %s @ %s'
+                  % (cls_name, self.required_perms, check_scope, check_location))
         if not user:
             log.debug('Empty User passed into arguments')
             return False
@@ -1005,18 +1223,19 @@
         ## process user
         if not isinstance(user, AuthUser):
             user = AuthUser(user.user_id)
-
-        if self.check_permissions(user.permissions, repo_name):
-            log.debug('Permission to %s granted for user: %s @ %s', repo_name,
-                      user, check_location)
+        if not check_location:
+            check_location = 'unspecified'
+        if self.check_permissions(user.permissions, repo_name, group_name):
+            log.debug('Permission to %s granted for user: %s @ %s'
+                      % (check_scope, user, check_location))
             return True
 
         else:
-            log.debug('Permission to %s denied for user: %s @ %s', repo_name,
-                      user, check_location)
+            log.debug('Permission to %s denied for user: %s @ %s'
+                      % (check_scope, user, check_location))
             return False
 
-    def check_permissions(self, perm_defs, repo_name):
+    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
         """
         implement in child class should return True if permissions are ok,
         False otherwise
@@ -1028,59 +1247,35 @@
 
 
 class HasPermissionAllApi(_BaseApiPerm):
-    def __call__(self, user, check_location=''):
-        return super(HasPermissionAllApi, self)\
-            .__call__(check_location=check_location, user=user)
-
-    def check_permissions(self, perm_defs, repo):
+    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
         if self.required_perms.issubset(perm_defs.get('global')):
             return True
         return False
 
 
 class HasPermissionAnyApi(_BaseApiPerm):
-    def __call__(self, user, check_location=''):
-        return super(HasPermissionAnyApi, self)\
-            .__call__(check_location=check_location, user=user)
-
-    def check_permissions(self, perm_defs, repo):
+    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
         if self.required_perms.intersection(perm_defs.get('global')):
             return True
         return False
 
 
 class HasRepoPermissionAllApi(_BaseApiPerm):
-    def __call__(self, user, repo_name, check_location=''):
-        return super(HasRepoPermissionAllApi, self)\
-            .__call__(check_location=check_location, user=user,
-                      repo_name=repo_name)
-
-    def check_permissions(self, perm_defs, repo_name):
-
+    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
         try:
-            self._user_perms = set(
-                [perm_defs['repositories'][repo_name]]
-            )
+            _user_perms = set([perm_defs['repositories'][repo_name]])
         except KeyError:
             log.warning(traceback.format_exc())
             return False
-        if self.required_perms.issubset(self._user_perms):
+        if self.required_perms.issubset(_user_perms):
             return True
         return False
 
 
 class HasRepoPermissionAnyApi(_BaseApiPerm):
-    def __call__(self, user, repo_name, check_location=''):
-        return super(HasRepoPermissionAnyApi, self)\
-            .__call__(check_location=check_location, user=user,
-                      repo_name=repo_name)
-
-    def check_permissions(self, perm_defs, repo_name):
-
+    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
         try:
-            _user_perms = set(
-                [perm_defs['repositories'][repo_name]]
-            )
+            _user_perms = set([perm_defs['repositories'][repo_name]])
         except KeyError:
             log.warning(traceback.format_exc())
             return False
@@ -1089,6 +1284,28 @@
         return False
 
 
+class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
+    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
+        try:
+            _user_perms = set([perm_defs['repositories_groups'][group_name]])
+        except KeyError:
+            log.warning(traceback.format_exc())
+            return False
+        if self.required_perms.intersection(_user_perms):
+            return True
+        return False
+
+class HasRepoGroupPermissionAllApi(_BaseApiPerm):
+    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
+        try:
+            _user_perms = set([perm_defs['repositories_groups'][group_name]])
+        except KeyError:
+            log.warning(traceback.format_exc())
+            return False
+        if self.required_perms.issubset(_user_perms):
+            return True
+        return False
+
 def check_ip_access(source_ip, allowed_ips=None):
     """
     Checks if source_ip is a subnet of any of allowed_ips.
@@ -1102,6 +1319,8 @@
         for ip in allowed_ips:
             try:
                 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
+                    log.debug('IP %s is network %s' %
+                              (ipaddr.IPAddress(source_ip), ipaddr.IPNetwork(ip)))
                     return True
                 # for any case we cannot determine the IP, don't crash just
                 # skip it and log as error, we want to say forbidden still when
--- a/rhodecode/lib/auth_ldap.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,167 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    rhodecode.controllers.changelog
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    RhodeCode authentication library for LDAP
-
-    :created_on: Created on Nov 17, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# 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 rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
-    LdapPasswordError, LdapImportError
-from rhodecode.lib.utils2 import safe_str
-
-log = logging.getLogger(__name__)
-
-
-try:
-    import ldap
-except ImportError:
-    # means that python-ldap is not installed
-    ldap = None
-
-
-class AuthLdap(object):
-
-    def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
-                 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
-                 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
-                 search_scope='SUBTREE', attr_login='uid'):
-        if ldap is None:
-            raise LdapImportError
-
-        self.ldap_version = ldap_version
-        ldap_server_type = 'ldap'
-
-        self.TLS_KIND = tls_kind
-
-        if self.TLS_KIND == 'LDAPS':
-            port = port or 689
-            ldap_server_type = ldap_server_type + 's'
-
-        OPT_X_TLS_DEMAND = 2
-        self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
-                                   OPT_X_TLS_DEMAND)
-        # split server into list
-        self.LDAP_SERVER_ADDRESS = server.split(',')
-        self.LDAP_SERVER_PORT = port
-
-        # USE FOR READ ONLY BIND TO LDAP SERVER
-        self.LDAP_BIND_DN = safe_str(bind_dn)
-        self.LDAP_BIND_PASS = safe_str(bind_pass)
-        _LDAP_SERVERS = []
-        for host in self.LDAP_SERVER_ADDRESS:
-            _LDAP_SERVERS.append("%s://%s:%s" % (ldap_server_type,
-                                                     host.replace(' ', ''),
-                                                     self.LDAP_SERVER_PORT))
-        self.LDAP_SERVER = str(', '.join(s for s in _LDAP_SERVERS))
-        self.BASE_DN = safe_str(base_dn)
-        self.LDAP_FILTER = safe_str(ldap_filter)
-        self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
-        self.attr_login = attr_login
-
-    def authenticate_ldap(self, username, password):
-        """
-        Authenticate a user via LDAP and return his/her LDAP properties.
-
-        Raises AuthenticationError if the credentials are rejected, or
-        EnvironmentError if the LDAP server can't be reached.
-
-        :param username: username
-        :param password: password
-        """
-
-        from rhodecode.lib.helpers import chop_at
-
-        uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
-
-        if not password:
-            log.debug("Attempt to authenticate LDAP user "
-                      "with blank password rejected.")
-            raise LdapPasswordError()
-        if "," in username:
-            raise LdapUsernameError("invalid character in username: ,")
-        try:
-            if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
-                ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
-                                '/etc/openldap/cacerts')
-            ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
-            ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
-            ldap.set_option(ldap.OPT_TIMEOUT, 20)
-            ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
-            ldap.set_option(ldap.OPT_TIMELIMIT, 15)
-            if self.TLS_KIND != 'PLAIN':
-                ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
-            server = ldap.initialize(self.LDAP_SERVER)
-            if self.ldap_version == 2:
-                server.protocol = ldap.VERSION2
-            else:
-                server.protocol = ldap.VERSION3
-
-            if self.TLS_KIND == 'START_TLS':
-                server.start_tls_s()
-
-            if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
-                log.debug('Trying simple_bind with password and given DN: %s'
-                          % self.LDAP_BIND_DN)
-                server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
-
-            filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
-                                     username)
-            log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
-                      filter_, self.LDAP_SERVER)
-            lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
-                                           filter_)
-
-            if not lobjects:
-                raise ldap.NO_SUCH_OBJECT()
-
-            for (dn, _attrs) in lobjects:
-                if dn is None:
-                    continue
-
-                try:
-                    log.debug('Trying simple bind with %s' % dn)
-                    server.simple_bind_s(dn, password)
-                    attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
-                                                '(objectClass=*)')[0][1]
-                    break
-
-                except ldap.INVALID_CREDENTIALS:
-                    log.debug(
-                        "LDAP rejected password for user '%s' (%s): %s" % (
-                            uid, username, dn
-                        )
-                    )
-
-            else:
-                log.debug("No matching LDAP objects for authentication "
-                          "of '%s' (%s)", uid, username)
-                raise LdapPasswordError()
-
-        except ldap.NO_SUCH_OBJECT:
-            log.debug("LDAP says no such user '%s' (%s)" % (uid, username))
-            raise LdapUsernameError()
-        except ldap.SERVER_DOWN:
-            raise LdapConnectionError("LDAP can't access "
-                                      "authentication server")
-
-        return (dn, attrs)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/auth_modules/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,414 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Authentication modules
+"""
+
+import logging
+import traceback
+
+from rhodecode.lib.compat import importlib
+from rhodecode.lib.utils2 import str2bool
+from rhodecode.lib.compat import formatted_json, hybrid_property
+from rhodecode.lib.auth import PasswordGenerator
+from rhodecode.model.user import UserModel
+from rhodecode.model.db import RhodeCodeSetting, User, UserGroup
+from rhodecode.model.meta import Session
+from rhodecode.model.user_group import UserGroupModel
+
+log = logging.getLogger(__name__)
+
+
+class LazyFormencode(object):
+    def __init__(self, formencode_obj, *args, **kwargs):
+        self.formencode_obj = formencode_obj
+        self.args = args
+        self.kwargs = kwargs
+
+    def __call__(self, *args, **kwargs):
+        from inspect import isfunction
+        formencode_obj = self.formencode_obj
+        if isfunction(formencode_obj):
+            #case we wrap validators into functions
+            formencode_obj = self.formencode_obj(*args, **kwargs)
+        return formencode_obj(*self.args, **self.kwargs)
+
+
+class RhodeCodeAuthPluginBase(object):
+    auth_func_attrs = {
+        "username": "unique username",
+        "firstname": "first name",
+        "lastname": "last name",
+        "email": "email address",
+        "groups": '["list", "of", "groups"]',
+        "extern_name": "name in external source of record",
+        "extern_type": "type of external source of record",
+        "admin": 'True|False defines if user should be RhodeCode super admin',
+        "active": 'True|False defines active state of user internally for RhodeCode',
+        "active_from_extern": "True|False\None, active state from the external auth, "
+                              "None means use definition from RhodeCode extern_type active value"
+    }
+
+    @property
+    def validators(self):
+        """
+        Exposes RhodeCode validators modules
+        """
+        # this is a hack to overcome issues with pylons threadlocals and
+        # translator object _() not beein registered properly.
+        class LazyCaller(object):
+            def __init__(self, name):
+                self.validator_name = name
+
+            def __call__(self, *args, **kwargs):
+                from rhodecode.model import validators as v
+                obj = getattr(v, self.validator_name)
+                #log.debug('Initializing lazy formencode object: %s' % obj)
+                return LazyFormencode(obj, *args, **kwargs)
+
+
+        class ProxyGet(object):
+            def __getattribute__(self, name):
+                return LazyCaller(name)
+
+        return ProxyGet()
+
+    @hybrid_property
+    def name(self):
+        """
+        Returns the name of this authentication plugin.
+
+        :returns: string
+        """
+        raise NotImplementedError("Not implemented in base class")
+
+    @hybrid_property
+    def is_container_auth(self):
+        """
+        Returns bool if this module uses container auth.
+
+        This property will trigger an automatic call to authenticate on
+        a visit to the website or during a push/pull.
+
+        :returns: bool
+        """
+        return False
+
+    def accepts(self, user, accepts_empty=True):
+        """
+        Checks if this authentication module should accept a request for
+        the current user.
+
+        :param user: user object fetched using plugin's get_user() method.
+        :param accepts_empty: if True accepts don't allow the user to be empty
+        :returns: boolean
+        """
+        plugin_name = self.name
+        if not user and not accepts_empty:
+            log.debug('User is empty not allowed to authenticate')
+            return False
+
+        if user and user.extern_type and user.extern_type != plugin_name:
+            log.debug('User %s should authenticate using %s this is %s, skipping'
+                      % (user, user.extern_type, plugin_name))
+
+            return False
+        return True
+
+    def get_user(self, username=None, **kwargs):
+        """
+        Helper method for user fetching in plugins, by default it's using
+        simple fetch by username, but this method can be custimized in plugins
+        eg. container auth plugin to fetch user by environ params
+
+        :param username: username if given to fetch from database
+        :param kwargs: extra arguments needed for user fetching.
+        """
+        user = None
+        log.debug('Trying to fetch user `%s` from RhodeCode database'
+                  % (username))
+        if username:
+            user = User.get_by_username(username)
+            if not user:
+                log.debug('Fallback to fetch user in case insensitive mode')
+                user = User.get_by_username(username, case_insensitive=True)
+        else:
+            log.debug('provided username:`%s` is empty skipping...' % username)
+        return user
+
+    def settings(self):
+        """
+        Return a list of the form:
+        [
+            {
+                "name": "OPTION_NAME",
+                "type": "[bool|password|string|int|select]",
+                ["values": ["opt1", "opt2", ...]]
+                "validator": "expr"
+                "description": "A short description of the option" [,
+                "default": Default Value],
+                ["formname": "Friendly Name for Forms"]
+            } [, ...]
+        ]
+
+        This is used to interrogate the authentication plugin as to what
+        settings it expects to be present and configured.
+
+        'type' is a shorthand notation for what kind of value this option is.
+        This is primarily used by the auth web form to control how the option
+        is configured.
+                bool : checkbox
+                password : password input box
+                string : input box
+                select : single select dropdown
+
+        'validator' is an lazy instantiated form field validator object, ala
+        formencode. You need to *call* this object to init the validators.
+        All calls to RhodeCode validators should be used through self.validators
+        which is a lazy loading proxy of formencode module.
+        """
+        raise NotImplementedError("Not implemented in base class")
+
+    def plugin_settings(self):
+        """
+        This method is called by the authentication framework, not the .settings()
+        method. This method adds a few default settings (e.g., "active"), so that
+        plugin authors don't have to maintain a bunch of boilerplate.
+
+        OVERRIDING THIS METHOD WILL CAUSE YOUR PLUGIN TO FAIL.
+        """
+
+        rcsettings = self.settings()
+        rcsettings.insert(0, {
+            "name": "enabled",
+            "validator": self.validators.StringBoolean(if_missing=False),
+            "type": "bool",
+            "description": "Enable or Disable this Authentication Plugin",
+            "formname": "Enabled"
+            }
+        )
+        return rcsettings
+
+    def user_activation_state(self):
+        """
+        Defines user activation state when creating new users
+
+        :returns: boolean
+        """
+        raise NotImplementedError("Not implemented in base class")
+
+    def auth(self, userobj, username, passwd, settings, **kwargs):
+        """
+        Given a user object (which may be null), username, a plaintext password,
+        and a settings object (containing all the keys needed as listed in settings()),
+        authenticate this user's login attempt.
+
+        Return None on failure. On success, return a dictionary of the form:
+
+            see: RhodeCodeAuthPluginBase.auth_func_attrs
+        This is later validated for correctness
+        """
+        raise NotImplementedError("not implemented in base class")
+
+    def _authenticate(self, userobj, username, passwd, settings, **kwargs):
+        """
+        Wrapper to call self.auth() that validates call on it
+
+        :param userobj: userobj
+        :param username: username
+        :param passwd: plaintext password
+        :param settings: plugin settings
+        """
+        auth = self.auth(userobj, username, passwd, settings, **kwargs)
+        if auth:
+            return self._validate_auth_return(auth)
+        return auth
+
+    def _validate_auth_return(self, ret):
+        if not isinstance(ret, dict):
+            raise Exception('returned value from auth must be a dict')
+        for k in self.auth_func_attrs:
+            if k not in ret:
+                raise Exception('Missing %s attribute from returned data' % k)
+        return ret
+
+
+class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
+    def use_fake_password(self):
+        """
+        Return a boolean that indicates whether or not we should set the user's
+        password to a random value when it is authenticated by this plugin.
+        If your plugin provides authentication, then you will generally want this.
+
+        :returns: boolean
+        """
+        raise NotImplementedError("Not implemented in base class")
+
+    def _authenticate(self, userobj, username, passwd, settings, **kwargs):
+        auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
+            userobj, username, passwd, settings, **kwargs)
+        if auth:
+            # maybe plugin will clean the username ?
+            # we should use the return value
+            username = auth['username']
+            # if user is not active from our extern type we should fail to authe
+            # this can prevent from creating users in RhodeCode when using
+            # external authentication, but if it's inactive user we shouldn't
+            # create that user anyway
+            if auth['active_from_extern'] is False:
+                log.warning("User %s authenticated against %s, but is inactive"
+                            % (username, self.__module__))
+                return None
+
+            if self.use_fake_password():
+                # Randomize the PW because we don't need it, but don't want
+                # them blank either
+                passwd = PasswordGenerator().gen_password(length=8)
+
+            log.debug('Updating or creating user info from %s plugin'
+                      % self.name)
+            user = UserModel().create_or_update(
+                username=username,
+                password=passwd,
+                email=auth["email"],
+                firstname=auth["firstname"],
+                lastname=auth["lastname"],
+                active=auth["active"],
+                admin=auth["admin"],
+                extern_name=auth["extern_name"],
+                extern_type=self.name
+            )
+            Session().flush()
+            # enforce user is just in given groups, all of them has to be ones
+            # created from plugins. We store this info in _group_data JSON field
+            try:
+                groups = auth['groups'] or []
+                UserGroupModel().enforce_groups(user, groups, self.name)
+            except Exception:
+                # for any reason group syncing fails, we should proceed with login
+                log.error(traceback.format_exc())
+            Session().commit()
+        return auth
+
+
+def importplugin(plugin):
+    """
+    Imports and returns the authentication plugin in the module named by plugin
+    (e.g., plugin='rhodecode.lib.auth_modules.auth_rhodecode'). Returns the
+    RhodeCodeAuthPluginBase subclass on success, raises exceptions on failure.
+
+    raises:
+        AttributeError -- no RhodeCodeAuthPlugin class in the module
+        TypeError -- if the RhodeCodeAuthPlugin is not a subclass of ours RhodeCodeAuthPluginBase
+        ImportError -- if we couldn't import the plugin at all
+    """
+    log.debug("Importing %s" % plugin)
+    PLUGIN_CLASS_NAME = "RhodeCodeAuthPlugin"
+    try:
+        module = importlib.import_module(plugin)
+    except (ImportError, TypeError):
+        log.error(traceback.format_exc())
+        # TODO: make this more error prone, if by some accident we screw up
+        # the plugin name, the crash is preatty bad and hard to recover
+        raise
+
+    log.debug("Loaded auth plugin from %s (module:%s, file:%s)"
+              % (plugin, module.__name__, module.__file__))
+
+    pluginclass = getattr(module, PLUGIN_CLASS_NAME)
+    if not issubclass(pluginclass, RhodeCodeAuthPluginBase):
+        raise TypeError("Authentication class %s.RhodeCodeAuthPlugin is not "
+                        "a subclass of %s" % (plugin, RhodeCodeAuthPluginBase))
+    return pluginclass
+
+
+def loadplugin(plugin):
+    """
+    Loads and returns an instantiated authentication plugin.
+
+        see: importplugin
+    """
+    plugin = importplugin(plugin)()
+    if plugin.plugin_settings.im_func != RhodeCodeAuthPluginBase.plugin_settings.im_func:
+        raise TypeError("Authentication class %s.RhodeCodeAuthPluginBase "
+                        "has overriden the plugin_settings method, which is "
+                        "forbidden." % plugin)
+    return plugin
+
+
+def authenticate(username, password, environ=None):
+    """
+    Authentication function used for access control,
+    It tries to authenticate based on enabled authentication modules.
+
+    :param username: username can be empty for container auth
+    :param password: password can be empty for container auth
+    :param environ: environ headers passed for container auth
+    :returns: None if auth failed, plugin_user dict if auth is correct
+    """
+
+    auth_plugins = RhodeCodeSetting.get_auth_plugins()
+    log.debug('Authentication against %s plugins' % (auth_plugins,))
+    for module in auth_plugins:
+        try:
+            plugin = loadplugin(module)
+        except (ImportError, AttributeError, TypeError), e:
+            raise ImportError('Failed to load authentication module %s : %s'
+                              % (module, str(e)))
+        log.debug('Trying authentication using ** %s **' % (module,))
+        # load plugin settings from RhodeCode database
+        plugin_name = plugin.name
+        plugin_settings = {}
+        for v in plugin.plugin_settings():
+            conf_key = "auth_%s_%s" % (plugin_name, v["name"])
+            setting = RhodeCodeSetting.get_by_name(conf_key)
+            plugin_settings[v["name"]] = setting.app_settings_value if setting else None
+        log.debug('Plugin settings \n%s' % formatted_json(plugin_settings))
+
+        if not str2bool(plugin_settings["enabled"]):
+            log.info("Authentication plugin %s is disabled, skipping for %s"
+                     % (module, username))
+            continue
+
+        # use plugin's method of user extraction.
+        user = plugin.get_user(username, environ=environ,
+                               settings=plugin_settings)
+        log.debug('Plugin %s extracted user is `%s`' % (module, user))
+        if not plugin.accepts(user):
+            log.debug('Plugin %s does not accept user `%s` for authentication'
+                      % (module, user))
+            continue
+        else:
+            log.debug('Plugin %s accepted user `%s` for authentication'
+                      % (module, user))
+
+        log.info('Authenticating user using %s plugin' % plugin.__module__)
+        # _authenticate is a wrapper for .auth() method of plugin.
+        # it checks if .auth() sends proper data. for RhodeCodeExternalAuthPlugin
+        # it also maps users to Database and maps the attributes returned
+        # from .auth() to RhodeCode database. If this function returns data
+        # then auth is correct.
+        plugin_user = plugin._authenticate(user, username, password,
+                                           plugin_settings,
+                                           environ=environ or {})
+        log.debug('PLUGIN USER DATA: %s' % plugin_user)
+
+        if plugin_user:
+            log.debug('Plugin returned proper authentication data')
+            return plugin_user
+
+        # we failed to Auth because .auth() method didn't return proper the user
+        log.warning("User `%s` failed to authenticate against %s"
+                    % (username, plugin.__module__))
+    return None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/auth_modules/auth_container.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.auth_modules.auth_container
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+RhodeCode container based authentication plugin
+
+:created_on: Created on Nov 17, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import logging
+from rhodecode.lib import auth_modules
+from rhodecode.lib.utils2 import str2bool, safe_unicode
+from rhodecode.lib.compat import hybrid_property
+from rhodecode.model.db import User
+
+log = logging.getLogger(__name__)
+
+
+class RhodeCodeAuthPlugin(auth_modules.RhodeCodeExternalAuthPlugin):
+    def __init__(self):
+        pass
+
+    @hybrid_property
+    def name(self):
+        return "container"
+
+    @hybrid_property
+    def is_container_auth(self):
+        return True
+
+    def settings(self):
+
+        settings = [
+            {
+                "name": "header",
+                "validator": self.validators.UnicodeString(strip=True, not_empty=True),
+                "type": "string",
+                "description": "Header to extract the user from",
+                "default": "REMOTE_USER",
+                "formname": "Header"
+            },
+            {
+                "name": "fallback_header",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "Header to extract the user from when main one fails",
+                "default": "HTTP_X_FORWARDED_USER",
+                "formname": "Fallback header"
+            },
+            {
+                "name": "clean_username",
+                "validator": self.validators.StringBoolean(if_missing=False),
+                "type": "bool",
+                "description": "Perform cleaning of user, if passed user has @ in username "
+                               "then first part before @ is taken. "
+                               "If there's \\ in the username only the part after \\ is taken",
+                "default": "True",
+                "formname": "Clean username"
+            },
+        ]
+        return settings
+
+    def use_fake_password(self):
+        return True
+
+    def user_activation_state(self):
+        def_user_perms = User.get_default_user().AuthUser.permissions['global']
+        return 'hg.extern_activate.auto' in def_user_perms
+
+    def _clean_username(self, username):
+        # Removing realm and domain from username
+        username = username.partition('@')[0]
+        username = username.rpartition('\\')[2]
+        return username
+
+    def _get_username(self, environ, settings):
+        username = None
+        environ = environ or {}
+        if not environ:
+            log.debug('got empty environ: %s' % environ)
+
+        settings = settings or {}
+        if settings.get('header'):
+            header = settings.get('header')
+            username = environ.get(header)
+            log.debug('extracted %s:%s' % (header, username))
+
+        # fallback mode
+        if not username and settings.get('fallback_header'):
+            header = settings.get('fallback_header')
+            username = environ.get(header)
+            log.debug('extracted %s:%s' % (header, username))
+
+        if username and str2bool(settings.get('clean_username')):
+            log.debug('Received username %s from container' % username)
+            username = self._clean_username(username)
+            log.debug('New cleanup user is: %s' % username)
+        return username
+
+    def get_user(self, username=None, **kwargs):
+        """
+        Helper method for user fetching in plugins, by default it's using
+        simple fetch by username, but this method can be custimized in plugins
+        eg. container auth plugin to fetch user by environ params
+        :param username: username if given to fetch
+        :param kwargs: extra arguments needed for user fetching.
+        """
+        environ = kwargs.get('environ') or {}
+        settings = kwargs.get('settings') or {}
+        username = self._get_username(environ, settings)
+        # we got the username, so use default method now
+        return super(RhodeCodeAuthPlugin, self).get_user(username)
+
+    def auth(self, userobj, username, password, settings, **kwargs):
+        """
+        Get's the container_auth username (or email). It tries to get username
+        from REMOTE_USER if this plugin is enabled, if that fails
+        it tries to get username from HTTP_X_FORWARDED_USER if fallback header
+        is set. clean_username extracts the username from this data if it's
+        having @ in it.
+        Return None on failure. On success, return a dictionary of the form:
+
+            see: RhodeCodeAuthPluginBase.auth_func_attrs
+
+        :param userobj:
+        :param username:
+        :param password:
+        :param settings:
+        :param kwargs:
+        """
+        environ = kwargs.get('environ')
+        if not environ:
+            log.debug('Empty environ data skipping...')
+            return None
+
+        if not userobj:
+            userobj = self.get_user('', environ=environ, settings=settings)
+
+        # we don't care passed username/password for container auth plugins.
+        # only way to log in is using environ
+        username = None
+        if userobj:
+            username = getattr(userobj, 'username')
+
+        if not username:
+            # we don't have any objects in DB user doesn't exist extrac username
+            # from environ based on the settings
+            username = self._get_username(environ, settings)
+
+        # if cannot fetch username, it's a no-go for this plugin to proceed
+        if not username:
+            return None
+
+        # old attrs fetched from RhodeCode database
+        admin = getattr(userobj, 'admin', False)
+        active = getattr(userobj, 'active', True)
+        email = getattr(userobj, 'email', '')
+        firstname = getattr(userobj, 'firstname', '')
+        lastname = getattr(userobj, 'lastname', '')
+        extern_type = getattr(userobj, 'extern_type', '')
+
+        user_attrs = {
+            'username': username,
+            'firstname': safe_unicode(firstname or username),
+            'lastname': safe_unicode(lastname or ''),
+            'groups': [],
+            'email': email or '',
+            'admin': admin or False,
+            'active': active,
+            'active_from_extern': True,
+            'extern_name': username,
+            'extern_type': extern_type,
+        }
+
+        log.info('user `%s` authenticated correctly' % user_attrs['username'])
+        return user_attrs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/auth_modules/auth_crowd.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,242 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.auth_modules.auth_crowd
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+RhodeCode authentication plugin for Atlassian CROWD
+
+:created_on: Created on Nov 17, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+
+import base64
+import logging
+import urllib2
+from rhodecode.lib import auth_modules
+from rhodecode.lib.compat import json, formatted_json, hybrid_property
+from rhodecode.model.db import User
+
+log = logging.getLogger(__name__)
+
+
+class CrowdServer(object):
+    def __init__(self, *args, **kwargs):
+        """
+        Create a new CrowdServer object that points to IP/Address 'host',
+        on the given port, and using the given method (https/http). user and
+        passwd can be set here or with set_credentials. If unspecified,
+        "version" defaults to "latest".
+
+        example::
+
+            cserver = CrowdServer(host="127.0.0.1",
+                                  port="8095",
+                                  user="some_app",
+                                  passwd="some_passwd",
+                                  version="1")
+        """
+        if not "port" in kwargs:
+            kwargs["port"] = "8095"
+        self._logger = kwargs.get("logger", logging.getLogger(__name__))
+        self._uri = "%s://%s:%s/crowd" % (kwargs.get("method", "http"),
+                                    kwargs.get("host", "127.0.0.1"),
+                                    kwargs.get("port", "8095"))
+        self.set_credentials(kwargs.get("user", ""),
+                             kwargs.get("passwd", ""))
+        self._version = kwargs.get("version", "latest")
+        self._url_list = None
+        self._appname = "crowd"
+
+    def set_credentials(self, user, passwd):
+        self.user = user
+        self.passwd = passwd
+        self._make_opener()
+
+    def _make_opener(self):
+        mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
+        mgr.add_password(None, self._uri, self.user, self.passwd)
+        handler = urllib2.HTTPBasicAuthHandler(mgr)
+        self.opener = urllib2.build_opener(handler)
+
+    def _request(self, url, body=None, headers=None,
+                 method=None, noformat=False,
+                 empty_response_ok=False):
+        _headers = {"Content-type": "application/json",
+                    "Accept": "application/json"}
+        if self.user and self.passwd:
+            authstring = base64.b64encode("%s:%s" % (self.user, self.passwd))
+            _headers["Authorization"] = "Basic %s" % authstring
+        if headers:
+            _headers.update(headers)
+        log.debug("Sent crowd: \n%s"
+                  % (formatted_json({"url": url, "body": body,
+                                           "headers": _headers})))
+        request = urllib2.Request(url, body, _headers)
+        if method:
+            request.get_method = lambda: method
+
+        global msg
+        msg = ""
+        try:
+            rdoc = self.opener.open(request)
+            msg = "".join(rdoc.readlines())
+            if not msg and empty_response_ok:
+                rval = {}
+                rval["status"] = True
+                rval["error"] = "Response body was empty"
+            elif not noformat:
+                rval = json.loads(msg)
+                rval["status"] = True
+            else:
+                rval = "".join(rdoc.readlines())
+        except Exception, e:
+            if not noformat:
+                rval = {"status": False,
+                        "body": body,
+                        "error": str(e) + "\n" + msg}
+            else:
+                rval = None
+        return rval
+
+    def user_auth(self, username, password):
+        """Authenticate a user against crowd. Returns brief information about
+        the user."""
+        url = ("%s/rest/usermanagement/%s/authentication?username=%s"
+               % (self._uri, self._version, username))
+        body = json.dumps({"value": password})
+        return self._request(url, body)
+
+    def user_groups(self, username):
+        """Retrieve a list of groups to which this user belongs."""
+        url = ("%s/rest/usermanagement/%s/user/group/nested?username=%s"
+               % (self._uri, self._version, username))
+        return self._request(url)
+
+
+class RhodeCodeAuthPlugin(auth_modules.RhodeCodeExternalAuthPlugin):
+
+    @hybrid_property
+    def name(self):
+        return "crowd"
+
+    def settings(self):
+        settings = [
+            {
+                "name": "host",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "The FQDN or IP of the Atlassian CROWD Server",
+                "default": "127.0.0.1",
+                "formname": "Host"
+            },
+            {
+                "name": "port",
+                "validator": self.validators.Number(strip=True),
+                "type": "int",
+                "description": "The Port in use by the Atlassian CROWD Server",
+                "default": 8095,
+                "formname": "Port"
+            },
+            {
+                "name": "app_name",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "The Application Name to authenticate to CROWD",
+                "default": "",
+                "formname": "Application Name"
+            },
+            {
+                "name": "app_password",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "The password to authenticate to CROWD",
+                "default": "",
+                "formname": "Application Password"
+            },
+            {
+                "name": "admin_groups",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "A comma separated list of group names that identify users as RhodeCode Administrators",
+                "formname": "Admin Groups"
+            }
+        ]
+        return settings
+
+    def use_fake_password(self):
+        return True
+
+    def user_activation_state(self):
+        def_user_perms = User.get_default_user().AuthUser.permissions['global']
+        return 'hg.extern_activate.auto' in def_user_perms
+
+    def auth(self, userobj, username, password, settings, **kwargs):
+        """
+        Given a user object (which may be null), username, a plaintext password,
+        and a settings object (containing all the keys needed as listed in settings()),
+        authenticate this user's login attempt.
+
+        Return None on failure. On success, return a dictionary of the form:
+
+            see: RhodeCodeAuthPluginBase.auth_func_attrs
+        This is later validated for correctness
+        """
+        if not username or not password:
+            log.debug('Empty username or password skipping...')
+            return None
+
+        log.debug("Crowd settings: \n%s" % (formatted_json(settings)))
+        server = CrowdServer(**settings)
+        server.set_credentials(settings["app_name"], settings["app_password"])
+        crowd_user = server.user_auth(username, password)
+        log.debug("Crowd returned: \n%s" % (formatted_json(crowd_user)))
+        if not crowd_user["status"]:
+            return None
+
+        res = server.user_groups(crowd_user["name"])
+        log.debug("Crowd groups: \n%s" % (formatted_json(res)))
+        crowd_user["groups"] = [x["name"] for x in res["groups"]]
+
+        # old attrs fetched from RhodeCode database
+        admin = getattr(userobj, 'admin', False)
+        active = getattr(userobj, 'active', True)
+        email = getattr(userobj, 'email', '')
+        firstname = getattr(userobj, 'firstname', '')
+        lastname = getattr(userobj, 'lastname', '')
+        extern_type = getattr(userobj, 'extern_type', '')
+
+        user_attrs = {
+            'username': username,
+            'firstname': crowd_user["first-name"] or firstname,
+            'lastname': crowd_user["last-name"] or lastname,
+            'groups': crowd_user["groups"],
+            'email': crowd_user["email"] or email,
+            'admin': admin,
+            'active': active,
+            'active_from_extern': crowd_user.get('active'),
+            'extern_name': crowd_user["name"],
+            'extern_type': extern_type,
+        }
+
+        # set an admin if we're in admin_groups of crowd
+        for group in settings["admin_groups"]:
+            if group in user_attrs["groups"]:
+                user_attrs["admin"] = True
+        log.debug("Final crowd user object: \n%s" % (formatted_json(user_attrs)))
+        log.info('user %s authenticated correctly' % user_attrs['username'])
+        return user_attrs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/auth_modules/auth_ldap.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,361 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.auth_modules.auth_ldap
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+RhodeCode authentication plugin for LDAP
+
+:created_on: Created on Nov 17, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+
+import logging
+import traceback
+
+from rhodecode.lib import auth_modules
+from rhodecode.lib.compat import hybrid_property
+from rhodecode.lib.utils2 import safe_unicode, safe_str
+from rhodecode.lib.exceptions import (
+    LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
+)
+from rhodecode.model.db import User
+
+log = logging.getLogger(__name__)
+
+try:
+    import ldap
+except ImportError:
+    # means that python-ldap is not installed
+    ldap = None
+
+
+class AuthLdap(object):
+
+    def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
+                 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
+                 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
+                 search_scope='SUBTREE', attr_login='uid'):
+        if ldap is None:
+            raise LdapImportError
+
+        self.ldap_version = ldap_version
+        ldap_server_type = 'ldap'
+
+        self.TLS_KIND = tls_kind
+
+        if self.TLS_KIND == 'LDAPS':
+            port = port or 689
+            ldap_server_type = ldap_server_type + 's'
+
+        OPT_X_TLS_DEMAND = 2
+        self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
+                                   OPT_X_TLS_DEMAND)
+        # split server into list
+        self.LDAP_SERVER_ADDRESS = server.split(',')
+        self.LDAP_SERVER_PORT = port
+
+        # USE FOR READ ONLY BIND TO LDAP SERVER
+        self.LDAP_BIND_DN = safe_str(bind_dn)
+        self.LDAP_BIND_PASS = safe_str(bind_pass)
+        _LDAP_SERVERS = []
+        for host in self.LDAP_SERVER_ADDRESS:
+            _LDAP_SERVERS.append("%s://%s:%s" % (ldap_server_type,
+                                                     host.replace(' ', ''),
+                                                     self.LDAP_SERVER_PORT))
+        self.LDAP_SERVER = str(', '.join(s for s in _LDAP_SERVERS))
+        self.BASE_DN = safe_str(base_dn)
+        self.LDAP_FILTER = safe_str(ldap_filter)
+        self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
+        self.attr_login = attr_login
+
+    def authenticate_ldap(self, username, password):
+        """
+        Authenticate a user via LDAP and return his/her LDAP properties.
+
+        Raises AuthenticationError if the credentials are rejected, or
+        EnvironmentError if the LDAP server can't be reached.
+
+        :param username: username
+        :param password: password
+        """
+
+        from rhodecode.lib.helpers import chop_at
+
+        uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
+
+        if not password:
+            log.debug("Attempt to authenticate LDAP user "
+                      "with blank password rejected.")
+            raise LdapPasswordError()
+        if "," in username:
+            raise LdapUsernameError("invalid character in username: ,")
+        try:
+            if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
+                ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
+                                '/etc/openldap/cacerts')
+            ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
+            ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
+            ldap.set_option(ldap.OPT_TIMEOUT, 20)
+            ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
+            ldap.set_option(ldap.OPT_TIMELIMIT, 15)
+            if self.TLS_KIND != 'PLAIN':
+                ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
+            server = ldap.initialize(self.LDAP_SERVER)
+            if self.ldap_version == 2:
+                server.protocol = ldap.VERSION2
+            else:
+                server.protocol = ldap.VERSION3
+
+            if self.TLS_KIND == 'START_TLS':
+                server.start_tls_s()
+
+            if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
+                log.debug('Trying simple_bind with password and given DN: %s'
+                          % self.LDAP_BIND_DN)
+                server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
+
+            filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
+                                        username)
+            log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
+                      filter_, self.LDAP_SERVER)
+            lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
+                                           filter_)
+
+            if not lobjects:
+                raise ldap.NO_SUCH_OBJECT()
+
+            for (dn, _attrs) in lobjects:
+                if dn is None:
+                    continue
+
+                try:
+                    log.debug('Trying simple bind with %s' % dn)
+                    server.simple_bind_s(dn, safe_str(password))
+                    attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
+                                                '(objectClass=*)')[0][1]
+                    break
+
+                except ldap.INVALID_CREDENTIALS:
+                    log.debug("LDAP rejected password for user '%s' (%s): %s"
+                              % (uid, username, dn))
+
+            else:
+                log.debug("No matching LDAP objects for authentication "
+                          "of '%s' (%s)", uid, username)
+                raise LdapPasswordError()
+
+        except ldap.NO_SUCH_OBJECT:
+            log.debug("LDAP says no such user '%s' (%s)" % (uid, username))
+            raise LdapUsernameError()
+        except ldap.SERVER_DOWN:
+            raise LdapConnectionError("LDAP can't access authentication server")
+
+        return dn, attrs
+
+
+class RhodeCodeAuthPlugin(auth_modules.RhodeCodeExternalAuthPlugin):
+    def __init__(self):
+        self._logger = logging.getLogger(__name__)
+        self._tls_kind_values = ["PLAIN", "LDAPS", "START_TLS"]
+        self._tls_reqcert_values = ["NEVER", "ALLOW", "TRY", "DEMAND", "HARD"]
+        self._search_scopes = ["BASE", "ONELEVEL", "SUBTREE"]
+
+    @hybrid_property
+    def name(self):
+        return "ldap"
+
+    def settings(self):
+        settings = [
+            {
+                "name": "host",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "Host of the LDAP Server",
+                "formname": "LDAP Host"
+            },
+            {
+                "name": "port",
+                "validator": self.validators.Number(strip=True, not_empty=True),
+                "type": "string",
+                "description": "Port that the LDAP server is listening on",
+                "default": 389,
+                "formname": "Port"
+            },
+            {
+                "name": "dn_user",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "User to connect to LDAP",
+                "formname": "Account"
+            },
+            {
+                "name": "dn_pass",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "password",
+                "description": "Password to connect to LDAP",
+                "formname": "Password"
+            },
+            {
+                "name": "tls_kind",
+                "validator": self.validators.OneOf(self._tls_kind_values),
+                "type": "select",
+                "values": self._tls_kind_values,
+                "description": "TLS Type",
+                "default": 'PLAIN',
+                "formname": "Connection Security"
+            },
+            {
+                "name": "tls_reqcert",
+                "validator": self.validators.OneOf(self._tls_reqcert_values),
+                "type": "select",
+                "values": self._tls_reqcert_values,
+                "description": "Require Cert over TLS?",
+                "formname": "Certificate Checks"
+            },
+            {
+                "name": "base_dn",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "Base DN to search (e.g., dc=mydomain,dc=com)",
+                "formname": "Base DN"
+            },
+            {
+                "name": "filter",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "Filter to narrow results (e.g., ou=Users, etc)",
+                "formname": "LDAP Search Filter"
+            },
+            {
+                "name": "search_scope",
+                "validator": self.validators.OneOf(self._search_scopes),
+                "type": "select",
+                "values": self._search_scopes,
+                "description": "How deep to search LDAP",
+                "formname": "LDAP Search Scope"
+            },
+            {
+                "name": "attr_login",
+                "validator": self.validators.AttrLoginValidator(not_empty=True, strip=True),
+                "type": "string",
+                "description": "LDAP Attribute to map to user name",
+                "formname": "Login Attribute"
+            },
+            {
+                "name": "attr_firstname",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "LDAP Attribute to map to first name",
+                "formname": "First Name Attribute"
+            },
+            {
+                "name": "attr_lastname",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "LDAP Attribute to map to last name",
+                "formname": "Last Name Attribute"
+            },
+            {
+                "name": "attr_email",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "LDAP Attribute to map to email address",
+                "formname": "Email Attribute"
+            }
+        ]
+        return settings
+
+    def use_fake_password(self):
+        return True
+
+    def user_activation_state(self):
+        def_user_perms = User.get_default_user().AuthUser.permissions['global']
+        return 'hg.extern_activate.auto' in def_user_perms
+
+    def auth(self, userobj, username, password, settings, **kwargs):
+        """
+        Given a user object (which may be null), username, a plaintext password,
+        and a settings object (containing all the keys needed as listed in settings()),
+        authenticate this user's login attempt.
+
+        Return None on failure. On success, return a dictionary of the form:
+
+            see: RhodeCodeAuthPluginBase.auth_func_attrs
+        This is later validated for correctness
+        """
+
+        if not username or not password:
+            log.debug('Empty username or password skipping...')
+            return None
+
+        kwargs = {
+            'server': settings.get('host', ''),
+            'base_dn': settings.get('base_dn', ''),
+            'port': settings.get('port'),
+            'bind_dn': settings.get('dn_user'),
+            'bind_pass': settings.get('dn_pass'),
+            'tls_kind': settings.get('tls_kind'),
+            'tls_reqcert': settings.get('tls_reqcert'),
+            'ldap_filter': settings.get('filter'),
+            'search_scope': settings.get('search_scope'),
+            'attr_login': settings.get('attr_login'),
+            'ldap_version': 3,
+        }
+
+        if kwargs['bind_dn'] and not kwargs['bind_pass']:
+            log.debug('Using dynamic binding.')
+            kwargs['bind_dn'] = kwargs['bind_dn'].replace('$login', username)
+            kwargs['bind_pass'] = password
+        log.debug('Checking for ldap authentication')
+
+        try:
+            aldap = AuthLdap(**kwargs)
+            (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
+            log.debug('Got ldap DN response %s' % user_dn)
+
+            get_ldap_attr = lambda k: ldap_attrs.get(settings.get(k), [''])[0]
+
+            # old attrs fetched from RhodeCode database
+            admin = getattr(userobj, 'admin', False)
+            active = getattr(userobj, 'active', True)
+            email = getattr(userobj, 'email', '')
+            firstname = getattr(userobj, 'firstname', '')
+            lastname = getattr(userobj, 'lastname', '')
+            extern_type = getattr(userobj, 'extern_type', '')
+
+            user_attrs = {
+                'username': username,
+                'firstname': safe_unicode(get_ldap_attr('attr_firstname') or firstname),
+                'lastname': safe_unicode(get_ldap_attr('attr_lastname') or lastname),
+                'groups': [],
+                'email': get_ldap_attr('attr_email' or email),
+                'admin': admin,
+                'active': active,
+                "active_from_extern": None,
+                'extern_name': user_dn,
+                'extern_type': extern_type,
+            }
+            log.info('user %s authenticated correctly' % user_attrs['username'])
+            return user_attrs
+
+        except (LdapUsernameError, LdapPasswordError, LdapImportError):
+            log.error(traceback.format_exc())
+            return None
+        except (Exception,):
+            log.error(traceback.format_exc())
+            return None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/auth_modules/auth_pam.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.auth_pam
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+RhodeCode authentication library for PAM
+
+:created_on: Created on Apr 09, 2013
+:author: Alexey Larikov
+"""
+
+import logging
+import time
+import pam
+import pwd
+import grp
+import re
+import socket
+import threading
+
+from rhodecode.lib import auth_modules
+from rhodecode.lib.compat import formatted_json, hybrid_property
+
+log = logging.getLogger(__name__)
+
+# Cache to store PAM authenticated users
+_auth_cache = dict()
+_pam_lock = threading.Lock()
+
+
+class RhodeCodeAuthPlugin(auth_modules.RhodeCodeExternalAuthPlugin):
+    # PAM authnetication can be slow. Repository operations involve a lot of
+    # auth calls. Little caching helps speedup push/pull operations significantly
+    AUTH_CACHE_TTL = 4
+
+    def __init__(self):
+        global _auth_cache
+        ts = time.time()
+        cleared_cache = dict(
+            [(k, v) for (k, v) in _auth_cache.items() if
+             (v + RhodeCodeAuthPlugin.AUTH_CACHE_TTL > ts)])
+        _auth_cache = cleared_cache
+
+    @hybrid_property
+    def name(self):
+        return "pam"
+
+    def settings(self):
+        settings = [
+            {
+                "name": "service",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "PAM service name to use for authentication",
+                "default": "login",
+                "formname": "PAM service name"
+            },
+            {
+                "name": "gecos",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "Regex for extracting user name/email etc "
+                               "from Unix userinfo",
+                "default": "(?P<last_name>.+),\s*(?P<first_name>\w+)",
+                "formname": "Gecos Regex"
+            }
+        ]
+        return settings
+
+    def use_fake_password(self):
+        return True
+
+    def auth(self, userobj, username, password, settings, **kwargs):
+        if username not in _auth_cache:
+            # Need lock here, as PAM authentication is not thread safe
+            _pam_lock.acquire()
+            try:
+                auth_result = pam.authenticate(username, password,
+                                               settings["service"])
+                # cache result only if we properly authenticated
+                if auth_result:
+                    _auth_cache[username] = time.time()
+            finally:
+                _pam_lock.release()
+
+            if not auth_result:
+                log.error("PAM was unable to authenticate user: %s" % (username,))
+                return None
+        else:
+            log.debug("Using cached auth for user: %s" % (username,))
+
+        # old attrs fetched from RhodeCode database
+        admin = getattr(userobj, 'admin', False)
+        active = getattr(userobj, 'active', True)
+        email = getattr(userobj, 'email', '') or "%s@%s" % (username, socket.gethostname())
+        firstname = getattr(userobj, 'firstname', '')
+        lastname = getattr(userobj, 'lastname', '')
+        extern_type = getattr(userobj, 'extern_type', '')
+
+        user_attrs = {
+            'username': username,
+            'firstname': firstname,
+            'lastname': lastname,
+            'groups': [g.gr_name for g in grp.getgrall() if username in g.gr_mem],
+            'email': email,
+            'admin': admin,
+            'active': active,
+            "active_from_extern": None,
+            'extern_name': username,
+            'extern_type': extern_type,
+        }
+
+        try:
+            user_data = pwd.getpwnam(username)
+            regex = settings["gecos"]
+            match = re.search(regex, user_data.pw_gecos)
+            if match:
+                user_attrs["firstname"] = match.group('first_name')
+                user_attrs["lastname"] = match.group('last_name')
+        except Exception:
+            log.warn("Cannot extract additional info for PAM user")
+            pass
+
+        log.debug("pamuser: \n%s" % formatted_json(user_attrs))
+        log.info('user %s authenticated correctly' % user_attrs['username'])
+        return user_attrs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/auth_modules/auth_rhodecode.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.auth_modules.auth_rhodecode
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+RhodeCode authentication plugin for built in internal auth
+
+:created_on: Created on Nov 17, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+
+import logging
+from rhodecode.lib import auth_modules
+from rhodecode.lib.compat import formatted_json, hybrid_property
+from rhodecode.model.db import User
+
+
+log = logging.getLogger(__name__)
+
+
+class RhodeCodeAuthPlugin(auth_modules.RhodeCodeAuthPluginBase):
+    def __init__(self):
+        pass
+
+    @hybrid_property
+    def name(self):
+        return "rhodecode"
+
+    def settings(self):
+        return []
+
+    def user_activation_state(self):
+        def_user_perms = User.get_default_user().AuthUser.permissions['global']
+        return 'hg.register.auto_activate' in def_user_perms
+
+    def accepts(self, user, accepts_empty=True):
+        """
+        Custom accepts for this auth that doesn't accept empty users. We
+        know that user exisits in database.
+        """
+        return super(RhodeCodeAuthPlugin, self).accepts(user,
+                                                        accepts_empty=False)
+
+    def auth(self, userobj, username, password, settings, **kwargs):
+        if not userobj:
+            log.debug('userobj was:%s skipping' % (userobj, ))
+            return None
+        if userobj.extern_type != self.name:
+            log.warn("userobj:%s extern_type mismatch got:`%s` expected:`%s`"
+                     % (userobj, userobj.extern_type, self.name))
+            return None
+
+        user_attrs = {
+            "username": userobj.username,
+            "firstname": userobj.firstname,
+            "lastname": userobj.lastname,
+            "groups": [],
+            "email": userobj.email,
+            "admin": userobj.admin,
+            "active": userobj.active,
+            "active_from_extern": userobj.active,
+            "extern_name": userobj.user_id,
+            'extern_type': userobj.extern_type,
+        }
+
+        log.debug(formatted_json(user_attrs))
+        if userobj.active:
+            from rhodecode.lib import auth
+            password_match = auth.RhodeCodeCrypto.hash_check(password, userobj.password)
+            if userobj.username == User.DEFAULT_USER and userobj.active:
+                log.info('user %s authenticated correctly as anonymous user' %
+                         username)
+                return user_attrs
+
+            elif userobj.username == username and password_match:
+                log.info('user %s authenticated correctly' % user_attrs['username'])
+                return user_attrs
+            log.error("user %s had a bad password" % username)
+            return None
+        else:
+            log.warning('user %s tried auth but is disabled' % username)
+            return None
--- a/rhodecode/lib/base.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/base.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,7 +1,31 @@
-"""The base Controller API
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-Provides the BaseController class for subclassing.
 """
+rhodecode.lib.base
+~~~~~~~~~~~~~~~~~~
+
+The base Controller API
+Provides the BaseController class for subclassing. And usage in different
+controllers
+
+:created_on: Oct 06, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import logging
 import time
 import traceback
@@ -13,14 +37,14 @@
 from pylons import config, tmpl_context as c, request, session, url
 from pylons.controllers import WSGIController
 from pylons.controllers.util import redirect
-from pylons.templating import render_mako as render
+from pylons.templating import render_mako as render  # don't remove this import
 
 from rhodecode import __version__, BACKENDS
 
 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
     safe_str, safe_int
-from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
-    HasPermissionAnyMiddleware, CookieStoreWrapper
+from rhodecode.lib import auth_modules
+from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware, CookieStoreWrapper
 from rhodecode.lib.utils import get_repo_slug
 from rhodecode.lib.exceptions import UserCreationError
 from rhodecode.model import meta
@@ -100,7 +124,7 @@
         _parts = auth.split(':', 1)
         if len(_parts) == 2:
             username, password = _parts
-            if self.authfunc(environ, username, password):
+            if self.authfunc(username, password, environ):
                 return username
         return self.build_authentication()
 
@@ -114,8 +138,8 @@
         self.config = config
         # base path of repo locations
         self.basepath = self.config['base_path']
-        #authenticate this mercurial request using authfunc
-        self.authenticate = BasicAuth('', authfunc,
+        #authenticate this VCS request using authfunc
+        self.authenticate = BasicAuth('', auth_modules.authenticate,
                                       config.get('auth_ret_code'))
         self.ip_addr = '0.0.0.0'
 
@@ -129,18 +153,13 @@
 
         :param repo_name:
         """
-        try:
-            data = repo_name.split('/')
-            if len(data) >= 2:
-                by_id = data[1].split('_')
-                if len(by_id) == 2 and by_id[1].isdigit():
-                    _repo_name = Repository.get(by_id[1]).repo_name
-                    data[1] = _repo_name
-        except Exception:
-            log.debug('Failed to extract repo_name from id %s' % (
-                      traceback.format_exc()
-                      )
-            )
+
+        data = repo_name.split('/')
+        if len(data) >= 2:
+            from rhodecode.lib.utils import get_repo_by_id
+            by_id_match = get_repo_by_id(repo_name)
+            if by_id_match:
+                data[1] = by_id_match
 
         return '/'.join(data)
 
@@ -161,12 +180,15 @@
         :param user: user instance
         :param repo_name: repository name
         """
-        #check IP
-        authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr)
-        if not authuser.ip_allowed:
+        # check IP
+        inherit = user.inherit_default_permissions
+        ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
+                                               inherit_from_default=inherit)
+        if ip_allowed:
+            log.info('Access for IP:%s allowed' % (ip_addr,))
+        else:
             return False
-        else:
-            log.info('Access for IP:%s allowed' % (ip_addr))
+
         if action == 'push':
             if not HasPermissionAnyMiddleware('repository.write',
                                               'repository.admin')(user,
@@ -261,31 +283,40 @@
         __before__ is called before controller methods and after __call__
         """
         c.rhodecode_version = __version__
-        c.rhodecode_instanceid = config.get('instance_id')
-        c.rhodecode_name = config.get('rhodecode_title')
-        c.rhodecode_bugtracker = config.get('bugtracker', 'http://bitbucket.org/marcinkuzminski/rhodecode/issues')
-        c.use_gravatar = str2bool(config.get('use_gravatar'))
-        c.ga_code = config.get('rhodecode_ga_code')
+        rc_config = RhodeCodeSetting.get_app_settings()
+
         # Visual options
         c.visual = AttributeDict({})
-        rc_config = RhodeCodeSetting.get_app_settings()
+
         ## DB stored
         c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon'))
         c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon'))
         c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags'))
         c.visual.dashboard_items = safe_int(rc_config.get('rhodecode_dashboard_items', 100))
+        c.visual.admin_grid_items = safe_int(rc_config.get('rhodecode_admin_grid_items', 100))
         c.visual.repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
         c.visual.show_version = str2bool(rc_config.get('rhodecode_show_version'))
+        c.visual.use_gravatar = str2bool(rc_config.get('rhodecode_use_gravatar'))
+        c.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
+
+        c.ga_code = rc_config.get('rhodecode_ga_code')
+        c.rhodecode_name = rc_config.get('rhodecode_title')
+        c.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
 
         ## INI stored
-        self.cut_off_limit = int(config.get('cut_off_limit'))
         c.visual.allow_repo_location_change = str2bool(config.get('allow_repo_location_change', True))
         c.visual.allow_custom_hooks_settings = str2bool(config.get('allow_custom_hooks_settings', True))
 
+        c.rhodecode_instanceid = config.get('instance_id')
+        c.rhodecode_bugtracker = config.get('bugtracker', url('rc_issue_tracker'))
+        # END CONFIG VARS
+
         c.repo_name = get_repo_slug(request)  # can be empty
         c.backends = BACKENDS.keys()
         c.unread_notifications = NotificationModel()\
                         .get_unread_cnt_for_user(c.rhodecode_user.user_id)
+
+        self.cut_off_limit = safe_int(config.get('cut_off_limit'))
         self.sa = meta.Session
         self.scm_model = ScmModel(self.sa)
 
@@ -298,27 +329,33 @@
             self.ip_addr = _get_ip_addr(environ)
             # make sure that we update permissions each time we call controller
             api_key = request.GET.get('api_key')
-            cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
-            user_id = cookie_store.get('user_id', None)
-            username = get_container_username(environ, config)
-            try:
-                auth_user = AuthUser(user_id, api_key, username, self.ip_addr)
-            except UserCreationError, e:
-                from rhodecode.lib import helpers as h
-                h.flash(e, 'error')
-                # container auth or other auth functions that create users on
-                # the fly can throw this exception signaling that there's issue
-                # with user creation, explanation should be provided in
-                # Exception itself
-                auth_user = AuthUser(ip_addr=self.ip_addr)
 
+            if api_key:
+                # when using API_KEY we are sure user exists.
+                auth_user = AuthUser(api_key=api_key, ip_addr=self.ip_addr)
+                authenticated = False
+            else:
+                cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
+                try:
+                    auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
+                                         ip_addr=self.ip_addr)
+                except UserCreationError, e:
+                    from rhodecode.lib import helpers as h
+                    h.flash(e, 'error')
+                    # container auth or other auth functions that create users on
+                    # the fly can throw this exception signaling that there's issue
+                    # with user creation, explanation should be provided in
+                    # Exception itself
+                    auth_user = AuthUser(ip_addr=self.ip_addr)
+
+                authenticated = cookie_store.get('is_authenticated')
+
+            if not auth_user.is_authenticated and auth_user.user_id is not None:
+                # user is not authenticated and not empty
+                auth_user.set_authenticated(authenticated)
             request.user = auth_user
+            #set globals for auth user
             self.rhodecode_user = c.rhodecode_user = auth_user
-            if not self.rhodecode_user.is_authenticated and \
-                       self.rhodecode_user.user_id is not None:
-                self.rhodecode_user.set_authenticated(
-                    cookie_store.get('is_authenticated')
-                )
             log.info('IP: %s User: %s accessed %s' % (
                self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
             )
@@ -341,21 +378,39 @@
 
     def __before__(self):
         super(BaseRepoController, self).__before__()
-        if c.repo_name:
+        if c.repo_name:  # extracted from routes
+            _dbr = Repository.get_by_repo_name(c.repo_name)
+            if not _dbr:
+                return
+
+            log.debug('Found repository in database %s with state `%s`'
+                      % (safe_unicode(_dbr), safe_unicode(_dbr.repo_state)))
+            route = getattr(request.environ.get('routes.route'), 'name', '')
 
-            dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
+            # allow to delete repos that are somehow damages in filesystem
+            if route in ['delete_repo']:
+                return
+
+            if _dbr.repo_state in [Repository.STATE_PENDING]:
+                if route in ['repo_creating_home']:
+                    return
+                check_url = url('repo_creating_home', repo_name=c.repo_name)
+                return redirect(check_url)
+
+            dbr = c.rhodecode_db_repo = _dbr
             c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
-            # update last change according to VCS data
-            dbr.update_changeset_cache(dbr.get_changeset())
             if c.rhodecode_repo is None:
                 log.error('%s this repository is present in database but it '
                           'cannot be created as an scm instance', c.repo_name)
 
                 redirect(url('home'))
 
+            # update last change according to VCS data
+            dbr.update_changeset_cache(dbr.get_changeset())
+
             # some globals counter for menu
             c.repository_followers = self.scm_model.get_followers(dbr)
             c.repository_forks = self.scm_model.get_forks(dbr)
             c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
-            c.repository_following = self.scm_model.is_following_repo(c.repo_name,
-                                                self.rhodecode_user.user_id)
+            c.repository_following = self.scm_model.is_following_repo(
+                                    c.repo_name, self.rhodecode_user.user_id)
--- a/rhodecode/lib/celerylib/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/celerylib/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.celerylib.__init__
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    celery libs for RhodeCode
-
-    :created_on: Nov 27, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.celerylib.__init__
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+celery libs for RhodeCode
+
+:created_on: Nov 27, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import os
 import sys
--- a/rhodecode/lib/celerylib/tasks.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/celerylib/tasks.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.celerylib.tasks
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    RhodeCode task modules, containing all task that suppose to be run
-    by celery daemon
-
-    :created_on: Oct 6, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,6 +11,19 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.celerylib.tasks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+RhodeCode task modules, containing all task that suppose to be run
+by celery daemon
+
+:created_on: Oct 6, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 from celery.decorators import task
 
 import os
@@ -53,7 +54,7 @@
 from rhodecode.model.scm import ScmModel
 
 
-add_cache(config)
+add_cache(config)  # pragma: no cover
 
 __all__ = ['whoosh_index', 'get_commits_stats',
            'reset_user_password', 'send_email']
@@ -63,7 +64,7 @@
     if CELERY_ON:
         try:
             log = cls.get_logger()
-        except:
+        except Exception:
             log = logging.getLogger(__name__)
     else:
         log = logging.getLogger(__name__)
@@ -299,8 +300,96 @@
         return False
     return True
 
+@task(ignore_result=False)
+@dbsession
+def create_repo(form_data, cur_user):
+    from rhodecode.model.repo import RepoModel
+    from rhodecode.model.user import UserModel
+    from rhodecode.model.db import RhodeCodeSetting
 
-@task(ignore_result=True)
+    log = get_logger(create_repo)
+    DBS = get_session()
+
+    cur_user = UserModel(DBS)._get_user(cur_user)
+
+    owner = cur_user
+    repo_name = form_data['repo_name']
+    repo_name_full = form_data['repo_name_full']
+    repo_type = form_data['repo_type']
+    description = form_data['repo_description']
+    private = form_data['repo_private']
+    clone_uri = form_data.get('clone_uri')
+    repo_group = form_data['repo_group']
+    landing_rev = form_data['repo_landing_rev']
+    copy_fork_permissions = form_data.get('copy_permissions')
+    copy_group_permissions = form_data.get('repo_copy_permissions')
+    fork_of = form_data.get('fork_parent_id')
+    state = form_data.get('repo_state', Repository.STATE_PENDING)
+
+    # repo creation defaults, private and repo_type are filled in form
+    defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
+    enable_statistics = defs.get('repo_enable_statistics')
+    enable_locking = defs.get('repo_enable_locking')
+    enable_downloads = defs.get('repo_enable_downloads')
+
+    try:
+        repo = RepoModel(DBS)._create_repo(
+            repo_name=repo_name_full,
+            repo_type=repo_type,
+            description=description,
+            owner=owner,
+            private=private,
+            clone_uri=clone_uri,
+            repo_group=repo_group,
+            landing_rev=landing_rev,
+            fork_of=fork_of,
+            copy_fork_permissions=copy_fork_permissions,
+            copy_group_permissions=copy_group_permissions,
+            enable_statistics=enable_statistics,
+            enable_locking=enable_locking,
+            enable_downloads=enable_downloads,
+            state=state
+        )
+
+        action_logger(cur_user, 'user_created_repo',
+                      form_data['repo_name_full'], '', DBS)
+
+        DBS.commit()
+        # now create this repo on Filesystem
+        RepoModel(DBS)._create_filesystem_repo(
+            repo_name=repo_name,
+            repo_type=repo_type,
+            repo_group=RepoModel(DBS)._get_repo_group(repo_group),
+            clone_uri=clone_uri,
+        )
+        repo = Repository.get_by_repo_name(repo_name_full)
+        log_create_repository(repo.get_dict(), created_by=owner.username)
+
+        # update repo changeset caches initially
+        repo.update_changeset_cache()
+
+        # set new created state
+        repo.set_state(Repository.STATE_CREATED)
+        DBS.commit()
+    except Exception, e:
+        log.warning('Exception %s occured when forking repository, '
+                    'doing cleanup...' % e)
+        # rollback things manually !
+        repo = Repository.get_by_repo_name(repo_name_full)
+        if repo:
+            Repository.delete(repo.repo_id)
+            DBS.commit()
+            RepoModel(DBS)._delete_filesystem_repo(repo)
+        raise
+
+    # it's an odd fix to make celery fail task when exception occurs
+    def on_failure(self, *args, **kwargs):
+        pass
+
+    return True
+
+
+@task(ignore_result=False)
 @dbsession
 def create_repo_fork(form_data, cur_user):
     """
@@ -318,60 +407,75 @@
     base_path = Repository.base_path()
     cur_user = UserModel(DBS)._get_user(cur_user)
 
-    fork_name = form_data['repo_name_full']
+    repo_name = form_data['repo_name']  # fork in this case
+    repo_name_full = form_data['repo_name_full']
+
     repo_type = form_data['repo_type']
-    description = form_data['description']
     owner = cur_user
     private = form_data['private']
     clone_uri = form_data.get('clone_uri')
-    repos_group = form_data['repo_group']
+    repo_group = form_data['repo_group']
     landing_rev = form_data['landing_rev']
     copy_fork_permissions = form_data.get('copy_permissions')
-    fork_of = RepoModel(DBS)._get_repo(form_data.get('fork_parent_id'))
 
-    fork_repo = RepoModel(DBS).create_repo(
-        fork_name, repo_type, description, owner, private, clone_uri,
-        repos_group, landing_rev, just_db=True, fork_of=fork_of,
-        copy_fork_permissions=copy_fork_permissions
-    )
+    try:
+        fork_of = RepoModel(DBS)._get_repo(form_data.get('fork_parent_id'))
 
-    update_after_clone = form_data['update_after_clone']
+        fork_repo = RepoModel(DBS)._create_repo(
+            repo_name=repo_name_full,
+            repo_type=repo_type,
+            description=form_data['description'],
+            owner=owner,
+            private=private,
+            clone_uri=clone_uri,
+            repo_group=repo_group,
+            landing_rev=landing_rev,
+            fork_of=fork_of,
+            copy_fork_permissions=copy_fork_permissions
+        )
+        action_logger(cur_user, 'user_forked_repo:%s' % repo_name_full,
+                      fork_of.repo_name, '', DBS)
+        DBS.commit()
 
-    source_repo_path = os.path.join(base_path, fork_of.repo_name)
-    destination_fork_path = os.path.join(base_path, fork_name)
-
-    log.info('creating fork of %s as %s', source_repo_path,
-             destination_fork_path)
-    backend = get_backend(repo_type)
+        update_after_clone = form_data['update_after_clone']
+        source_repo_path = os.path.join(base_path, fork_of.repo_name)
 
-    if repo_type == 'git':
-        r = backend(safe_str(destination_fork_path), create=True,
-                src_url=safe_str(source_repo_path),
-                update_after_clone=update_after_clone,
-                bare=True)
-        # add rhodecode hook into this repo
-        ScmModel().install_git_hook(repo=r)
-    elif repo_type == 'hg':
-        r = backend(safe_str(destination_fork_path), create=True,
-                src_url=safe_str(source_repo_path),
-                update_after_clone=update_after_clone)
-    else:
-        raise Exception('Unknown backend type %s' % repo_type)
+        # now create this repo on Filesystem
+        RepoModel(DBS)._create_filesystem_repo(
+            repo_name=repo_name,
+            repo_type=repo_type,
+            repo_group=RepoModel(DBS)._get_repo_group(repo_group),
+            clone_uri=source_repo_path,
+        )
+        repo = Repository.get_by_repo_name(repo_name_full)
+        log_create_repository(repo.get_dict(), created_by=owner.username)
+
+        # update repo changeset caches initially
+        repo.update_changeset_cache()
 
-    log_create_repository(fork_repo.get_dict(), created_by=cur_user.username)
-
-    action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
-                   fork_of.repo_name, '', DBS)
+        # set new created state
+        repo.set_state(Repository.STATE_CREATED)
+        DBS.commit()
+    except Exception, e:
+        log.warning('Exception %s occured when forking repository, '
+                    'doing cleanup...' % e)
+        #rollback things manually !
+        repo = Repository.get_by_repo_name(repo_name_full)
+        if repo:
+            Repository.delete(repo.repo_id)
+            DBS.commit()
+            RepoModel(DBS)._delete_filesystem_repo(repo)
+        raise
 
-    action_logger(cur_user, 'user_created_fork:%s' % fork_name,
-                   fork_name, '', DBS)
-    # finally commit at latest possible stage
-    DBS.commit()
-    fork_repo.update_changeset_cache()
+    # it's an odd fix to make celery fail task when exception occurs
+    def on_failure(self, *args, **kwargs):
+        pass
+
+    return True
 
 
 def __get_codes_stats(repo_name):
-    from rhodecode.config.conf import  LANGUAGES_EXTENSIONS_MAP
+    from rhodecode.config.conf import LANGUAGES_EXTENSIONS_MAP
     repo = Repository.get_by_repo_name(repo_name).scm_instance
 
     tip = repo.get_changeset()
--- a/rhodecode/lib/celerypylons/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/celerypylons/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
 """
 Automatically sets the environment variable `CELERY_LOADER` to
 `celerypylons.loader:PylonsLoader`.  This ensures the loader is
@@ -7,6 +9,7 @@
     import celerypylons
 
 """
+
 import os
 import warnings
 
--- a/rhodecode/lib/celerypylons/commands.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/celerypylons/commands.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
 import rhodecode
 from rhodecode.lib.utils import BasePasterCommand, Command, load_rcextensions
 from celery.app import app_or_default
@@ -36,7 +38,7 @@
             CELERY_ON = False
 
         if not CELERY_ON:
-            raise Exception('Please enable celery_on in .ini config '
+            raise Exception('Please set use_celery = true in .ini config '
                             'file before running celeryd')
         rhodecode.CELERY_ON = CELERY_ON
         load_rcextensions(config['here'])
--- a/rhodecode/lib/celerypylons/loader.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/celerypylons/loader.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
 from celery.loaders.base import BaseLoader
 from pylons import config
 
--- a/rhodecode/lib/colored_formatter.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/colored_formatter.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,16 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# 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
 
--- a/rhodecode/lib/compat.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/compat.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.compat
-    ~~~~~~~~~~~~~~~~~~~~
-
-    Python backward compatibility functions and common libs
-
-
-    :created_on: Oct 7, 2011
-    :author: marcink
-    :copyright: (C) 2010-2010 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,8 +11,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/>.
+"""
+rhodecode.lib.compat
+~~~~~~~~~~~~~~~~~~~~
+
+Python backward compatibility functions and common libs
+
+
+:created_on: Oct 7, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import os
+import functools
+import importlib
 from rhodecode import __py_version__, is_windows
 
 #==============================================================================
@@ -32,6 +35,9 @@
 #==============================================================================
 from rhodecode.lib.ext_json import json
 
+# alias for formatted json
+formatted_json = functools.partial(json.dumps, indent=4, sort_keys=True)
+
 if __py_version__ >= (2, 7):
     import unittest
 else:
@@ -367,6 +373,12 @@
 
 
 #==============================================================================
+# Hybrid property/method
+#==============================================================================
+from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
+
+
+#==============================================================================
 # kill FUNCTIONS
 #==============================================================================
 if is_windows:
--- a/rhodecode/lib/db_manage.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/db_manage.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.db_manage
-    ~~~~~~~~~~~~~~~~~~~~~~~
-
-    Database creation, and setup module for RhodeCode. Used for creation
-    of database as well as for migration operations
-
-    :created_on: Apr 10, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.db_manage
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Database creation, and setup module for RhodeCode. Used for creation
+of database as well as for migration operations
+
+:created_on: Apr 10, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import os
 import sys
@@ -39,15 +39,15 @@
 from rhodecode.model import init_model
 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
     RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
-    UserRepoGroupToPerm, CacheInvalidation, UserGroup
+    UserRepoGroupToPerm, CacheInvalidation, UserGroup, Repository
 
 from sqlalchemy.engine import create_engine
-from rhodecode.model.repos_group import ReposGroupModel
+from rhodecode.model.repo_group import RepoGroupModel
 #from rhodecode.model import meta
 from rhodecode.model.meta import Session, Base
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.permission import PermissionModel
-from rhodecode.model.users_group import UserGroupModel
+from rhodecode.model.user_group import UserGroupModel
 
 
 log = logging.getLogger(__name__)
@@ -61,172 +61,8 @@
     print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
 
 
-class UpgradeSteps(object):
-    """
-    Those steps follow schema versions so for example schema
-    for example schema with seq 002 == step_2 and so on.
-    """
-
-    def __init__(self, klass):
-        self.klass = klass
-
-    def step_1(self):
-        pass
-
-    def step_2(self):
-        notify('Patching repo paths for newer version of RhodeCode')
-        self.klass.fix_repo_paths()
-
-        notify('Patching default user of RhodeCode')
-        self.klass.fix_default_user()
-
-        log.info('Changing ui settings')
-        self.klass.create_ui_settings()
-
-    def step_3(self):
-        notify('Adding additional settings into RhodeCode db')
-        self.klass.fix_settings()
-        notify('Adding ldap defaults')
-        self.klass.create_ldap_options(skip_existing=True)
-
-    def step_4(self):
-        notify('create permissions and fix groups')
-        self.klass.create_permissions()
-        self.klass.fixup_groups()
-
-    def step_5(self):
-        pass
-
-    def step_6(self):
-
-        notify('re-checking permissions')
-        self.klass.create_permissions()
-
-        notify('installing new UI options')
-        sett4 = RhodeCodeSetting('show_public_icon', True)
-        Session().add(sett4)
-        sett5 = RhodeCodeSetting('show_private_icon', True)
-        Session().add(sett5)
-        sett6 = RhodeCodeSetting('stylify_metatags', False)
-        Session().add(sett6)
-
-        notify('fixing old PULL hook')
-        _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
-        if _pull:
-            _pull.ui_key = RhodeCodeUi.HOOK_PULL
-            Session().add(_pull)
-
-        notify('fixing old PUSH hook')
-        _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
-        if _push:
-            _push.ui_key = RhodeCodeUi.HOOK_PUSH
-            Session().add(_push)
-
-        notify('installing new pre-push hook')
-        hooks4 = RhodeCodeUi()
-        hooks4.ui_section = 'hooks'
-        hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
-        hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
-        Session().add(hooks4)
-
-        notify('installing new pre-pull hook')
-        hooks6 = RhodeCodeUi()
-        hooks6.ui_section = 'hooks'
-        hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
-        hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
-        Session().add(hooks6)
-
-        notify('installing hgsubversion option')
-        # enable hgsubversion disabled by default
-        hgsubversion = RhodeCodeUi()
-        hgsubversion.ui_section = 'extensions'
-        hgsubversion.ui_key = 'hgsubversion'
-        hgsubversion.ui_value = ''
-        hgsubversion.ui_active = False
-        Session().add(hgsubversion)
-
-        notify('installing hg git option')
-        # enable hggit disabled by default
-        hggit = RhodeCodeUi()
-        hggit.ui_section = 'extensions'
-        hggit.ui_key = 'hggit'
-        hggit.ui_value = ''
-        hggit.ui_active = False
-        Session().add(hggit)
-
-        notify('re-check default permissions')
-        default_user = User.get_by_username(User.DEFAULT_USER)
-        perm = Permission.get_by_key('hg.fork.repository')
-        reg_perm = UserToPerm()
-        reg_perm.user = default_user
-        reg_perm.permission = perm
-        Session().add(reg_perm)
-
-    def step_7(self):
-        perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
-        Session().commit()
-        if perm_fixes:
-            notify('There was an inconsistent state of permissions '
-                   'detected for default user. Permissions are now '
-                   'reset to the default value for default user. '
-                   'Please validate and check default permissions '
-                   'in admin panel')
-
-    def step_8(self):
-        self.klass.create_permissions()
-        self.klass.populate_default_permissions()
-        self.klass.create_default_options(skip_existing=True)
-        Session().commit()
-
-    def step_9(self):
-        pass
-
-    def step_10(self):
-        pass
-
-    def step_11(self):
-        self.klass.update_repo_info()
-
-    def step_12(self):
-        self.klass.create_permissions()
-        Session().commit()
-
-        self.klass.populate_default_permissions()
-        Session().commit()
-
-        #fix all usergroups
-        ug_model = UserGroupModel()
-        for ug in UserGroup.get_all():
-            perm_obj = ug_model._create_default_perms(ug)
-            Session().add(perm_obj)
-        Session().commit()
-
-        adm = User.get_first_admin()
-        # fix owners of UserGroup
-        for ug in Session().query(UserGroup).all():
-            ug.user_id = adm.user_id
-            Session().add(ug)
-        Session().commit()
-
-        # fix owners of RepoGroup
-        for ug in Session().query(RepoGroup).all():
-            ug.user_id = adm.user_id
-            Session().add(ug)
-        Session().commit()
-
-    def step_13(self):
-        pass
-
-    def step_14(self):
-        # fix nullable columns on last_update
-        for r in RepoModel().get_all():
-            if r.updated_on is None:
-                r.updated_on = datetime.datetime.fromtimestamp(0)
-                Session().add(r)
-        Session().commit()
-
 class DbManage(object):
-    def __init__(self, log_sql, dbconf, root, tests=False, cli_args={}):
+    def __init__(self, log_sql, dbconf, root, tests=False, SESSION=None, cli_args={}):
         self.dbname = dbconf.split('/')[-1]
         self.tests = tests
         self.root = root
@@ -234,17 +70,21 @@
         self.log_sql = log_sql
         self.db_exists = False
         self.cli_args = cli_args
-        self.init_db()
+        self.init_db(SESSION=SESSION)
 
         force_ask = self.cli_args.get('force_ask')
         if force_ask is not None:
             global ask_ok
             ask_ok = lambda *args, **kwargs: force_ask
 
-    def init_db(self):
-        engine = create_engine(self.dburi, echo=self.log_sql)
-        init_model(engine)
-        self.sa = Session()
+    def init_db(self, SESSION=None):
+        if SESSION:
+            self.sa = SESSION
+        else:
+            #init new sessions
+            engine = create_engine(self.dburi, echo=self.log_sql)
+            init_model(engine)
+            self.sa = Session()
 
     def create_tables(self, override=False):
         """
@@ -257,7 +97,8 @@
         else:
             destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
         if not destroy:
-            sys.exit('Nothing tables created')
+            print 'Nothing done.'
+            sys.exit(0)
         if destroy:
             Base.metadata.drop_all()
 
@@ -289,13 +130,14 @@
                '********************** WARNING **********************\n'
                'Make sure your version of sqlite is at least 3.7.X.  \n'
                'Earlier versions are known to fail on some migrations\n'
-               '*****************************************************\n'
-            )
+               '*****************************************************\n')
+
         upgrade = ask_ok('You are about to perform database upgrade, make '
                          'sure You backed up your database before. '
                          'Continue ? [y/n]')
         if not upgrade:
-            sys.exit('No upgrade performed')
+            print 'No upgrade performed'
+            sys.exit(0)
 
         repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
                              'rhodecode/lib/dbmigrate')
@@ -303,19 +145,19 @@
 
         try:
             curr_version = api.db_version(db_uri, repository_path)
-            msg = ('Found current database under version'
-                 ' control with version %s' % curr_version)
+            msg = ('Found current database under version '
+                   'control with version %s' % curr_version)
 
         except (RuntimeError, DatabaseNotControlledError):
             curr_version = 1
-            msg = ('Current database is not under version control. Setting'
-                   ' as version %s' % curr_version)
+            msg = ('Current database is not under version control. Setting '
+                   'as version %s' % curr_version)
             api.version_control(db_uri, repository_path, curr_version)
 
         notify(msg)
-
         if curr_version == __dbversion__:
-            sys.exit('This database is already at the newest version')
+            print 'This database is already at the newest version'
+            sys.exit(0)
 
         # clear cache keys
         log.info("Clearing cache keys now...")
@@ -329,16 +171,11 @@
         _step = None
         for step in upgrade_steps:
             notify('performing upgrade step %s' % step)
-            time.sleep(2)
+            time.sleep(0.5)
 
             api.upgrade(db_uri, repository_path, step)
             notify('schema upgrade for step %s completed' % (step,))
 
-            fixture = 'step_%s' % step
-            notify('performing fixture step %s' % fixture)
-            getattr(UpgradeSteps(self), fixture)()
-            self.sa.commit()
-            notify('fixture %s completed' % (fixture,))
             _step = step
 
         notify('upgrade to version %s successful' % _step)
@@ -367,7 +204,7 @@
         used mostly for anonymous access
         """
         def_user = self.sa.query(User)\
-                .filter(User.username == 'default')\
+                .filter(User.username == User.DEFAULT_USER)\
                 .one()
 
         def_user.name = 'Anonymous'
@@ -447,7 +284,7 @@
             self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
                              TEST_USER_REGULAR2_EMAIL, False)
 
-    def create_ui_settings(self):
+    def create_ui_settings(self, repo_store_path):
         """
         Creates ui settings, fills out hooks
         and disables dotencode
@@ -505,6 +342,15 @@
         largefiles.ui_value = ''
         self.sa.add(largefiles)
 
+        # set default largefiles cache dir, defaults to
+        # /repo location/.cache/largefiles
+        largefiles = RhodeCodeUi()
+        largefiles.ui_section = 'largefiles'
+        largefiles.ui_key = 'usercache'
+        largefiles.ui_value = os.path.join(repo_store_path, '.cache',
+                                           'largefiles')
+        self.sa.add(largefiles)
+
         # enable hgsubversion disabled by default
         hgsubversion = RhodeCodeUi()
         hgsubversion.ui_section = 'extensions'
@@ -521,37 +367,35 @@
         hggit.ui_active = False
         self.sa.add(hggit)
 
-    def create_ldap_options(self, skip_existing=False):
-        """Creates ldap settings"""
+    def create_auth_plugin_options(self, skip_existing=False):
+        """
+        Create default auth plugin settings, and make it active
 
-        for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
-                    ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
-                    ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
-                    ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
-                    ('ldap_filter', ''), ('ldap_search_scope', ''),
-                    ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
-                    ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
+        :param skip_existing:
+        """
 
-            if skip_existing and RhodeCodeSetting.get_by_name(k) is not None:
+        for k, v, t in [('auth_plugins', 'rhodecode.lib.auth_modules.auth_rhodecode', 'list'),
+                     ('auth_rhodecode_enabled', 'True', 'bool')]:
+            if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
                 log.debug('Skipping option %s' % k)
                 continue
-            setting = RhodeCodeSetting(k, v)
+            setting = RhodeCodeSetting(k, v, t)
             self.sa.add(setting)
 
     def create_default_options(self, skip_existing=False):
         """Creates default settings"""
 
-        for k, v in [
-            ('default_repo_enable_locking',  False),
-            ('default_repo_enable_downloads', False),
-            ('default_repo_enable_statistics', False),
-            ('default_repo_private', False),
-            ('default_repo_type', 'hg')]:
+        for k, v, t in [
+            ('default_repo_enable_locking',  False, 'bool'),
+            ('default_repo_enable_downloads', False, 'bool'),
+            ('default_repo_enable_statistics', False, 'bool'),
+            ('default_repo_private', False, 'bool'),
+            ('default_repo_type', 'hg', 'unicode')]:
 
             if skip_existing and RhodeCodeSetting.get_by_name(k) is not None:
                 log.debug('Skipping option %s' % k)
                 continue
-            setting = RhodeCodeSetting(k, v)
+            setting = RhodeCodeSetting(k, v, t)
             self.sa.add(setting)
 
     def fixup_groups(self):
@@ -567,7 +411,7 @@
 
             if default is None:
                 log.debug('missing default permission for group %s adding' % g)
-                perm_obj = ReposGroupModel()._create_default_perms(g)
+                perm_obj = RepoGroupModel()._create_default_perms(g)
                 self.sa.add(perm_obj)
 
     def reset_permissions(self, username):
@@ -651,7 +495,7 @@
 
     def create_settings(self, path):
 
-        self.create_ui_settings()
+        self.create_ui_settings(path)
 
         ui_config = [
             ('web', 'push_ssl', 'false'),
@@ -669,20 +513,25 @@
             self.sa.add(ui_conf)
 
         settings = [
-            ('realm', 'RhodeCode authentication', unicode),
-            ('title', 'RhodeCode', unicode),
-            ('ga_code', '', unicode),
-            ('show_public_icon', True, bool),
-            ('show_private_icon', True, bool),
-            ('stylify_metatags', False, bool),
-            ('dashboard_items', 100, int),
-            ('show_version', True, bool)
+            ('realm', 'RhodeCode', 'unicode'),
+            ('title', '', 'unicode'),
+            ('ga_code', '', 'unicode'),
+            ('show_public_icon', True, 'bool'),
+            ('show_private_icon', True, 'bool'),
+            ('stylify_metatags', False, 'bool'),
+            ('dashboard_items', 100, 'int'),
+            ('admin_grid_items', 25, 'int'),
+            ('show_version', True, 'bool'),
+            ('use_gravatar', True, 'bool'),
+            ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
+            ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
+            ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
         ]
         for key, val, type_ in settings:
-            sett = RhodeCodeSetting(key, val)
+            sett = RhodeCodeSetting(key, val, type_)
             self.sa.add(sett)
 
-        self.create_ldap_options()
+        self.create_auth_plugin_options()
         self.create_default_options()
 
         log.info('created ui config')
@@ -691,7 +540,8 @@
         log.info('creating user %s' % username)
         UserModel().create_or_update(username, password, email,
                                      firstname='RhodeCode', lastname='Admin',
-                                     active=True, admin=admin)
+                                     active=True, admin=admin,
+                                     extern_type="rhodecode")
 
     def create_default_user(self):
         log.info('creating default user')
--- a/rhodecode/lib/dbmigrate/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.dbmigrate.__init__
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Database migration modules
-
-    :created_on: Dec 11, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.dbmigrate.__init__
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Database migration modules
+
+:created_on: Dec 11, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import logging
 from sqlalchemy import engine_from_config
@@ -48,14 +48,12 @@
 
     def command(self):
         from pylons import config
-
         add_cache(config)
 
         db_uri = config['sqlalchemy.db1.url']
-
         dbmanage = DbManage(log_sql=True, dbconf=db_uri,
-                            root=config['here'], tests=False)
-
+                            root=config['here'], tests=False,
+                            cli_args=self.options.__dict__)
         dbmanage.upgrade()
 
     def update_parser(self):
@@ -64,3 +62,14 @@
                       dest='just_sql',
                       help="Prints upgrade sql for further investigation",
                       default=False)
+
+        self.parser.add_option('--force-yes',
+                           action='store_true',
+                           dest='force_ask',
+                           default=None,
+                           help='Force yes to every question')
+        self.parser.add_option('--force-no',
+                           action='store_false',
+                           dest='force_ask',
+                           default=None,
+                           help='Force no to every question')
--- a/rhodecode/lib/dbmigrate/schema/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/schema/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,14 +1,24 @@
 # -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
-    rhodecode.lib.dbmigrate.schema
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
+rhodecode.lib.dbmigrate.schema
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    Schemas for migrations
-
+Schemas for migrations
 
-    :created_on: Nov 1, 2011
-    :author: marcink
-    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
-    :license: <name>, see LICENSE_FILE for more details.
+:created_on: Nov 1, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
 """
--- a/rhodecode/lib/dbmigrate/schema/db_1_2_0.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.db_1_2_0
-    ~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Database Models for RhodeCode <=1.2.X
-
-    :created_on: Apr 08, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db_1_2_0
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode <=1.2.X
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import os
 import logging
@@ -903,7 +903,7 @@
             raise Exception('perm needs to be an instance of Permission class')
 
         try:
-            cls.query().filter(cls.user_id == user_id)\
+            cls.query().filter(cls.user_id == user_id) \
                 .filter(cls.permission == perm).delete()
             Session.commit()
         except:
@@ -966,7 +966,7 @@
             raise Exception('perm needs to be an instance of Permission class')
 
         try:
-            cls.query().filter(cls.users_group_id == users_group_id)\
+            cls.query().filter(cls.users_group_id == users_group_id) \
                 .filter(cls.permission == perm).delete()
             Session.commit()
         except:
--- a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.db_1_3_0
-    ~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Database Models for RhodeCode <=1.3.X
-
-    :created_on: Apr 08, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,19 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db_1_3_0
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode <=1.3.X
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+"""
+
 
 import os
 import logging
--- a/rhodecode/lib/dbmigrate/schema/db_1_4_0.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/schema/db_1_4_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.db_1_4_0
-    ~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Database Models for RhodeCode <=1.4.X
-
-    :created_on: Apr 08, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db_1_4_0
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode <=1.4.X
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import os
 import logging
--- a/rhodecode/lib/dbmigrate/schema/db_1_5_0.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/schema/db_1_5_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.db_1_5_0
-    ~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Database Models for RhodeCode <=1.5.2
-
-    :created_on: Apr 08, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db_1_5_0
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode <=1.5.2
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import os
 import logging
@@ -1221,6 +1221,14 @@
         'hg.create.repository':1
     }
 
+    DEFAULT_USER_PERMISSIONS = [
+        'repository.read',
+        'group.read',
+        'hg.create.repository',
+        'hg.fork.repository',
+        'hg.register.manual_activate',
+    ]
+
     permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
     permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
--- a/rhodecode/lib/dbmigrate/schema/db_1_5_2.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/schema/db_1_5_2.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.db_1_5_2
-    ~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Database Models for RhodeCode <=1.5.X
-
-    :created_on: Apr 08, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db_1_5_2
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode <=1.5.X
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import os
 import logging
@@ -701,7 +701,7 @@
 
     def __unicode__(self):
         return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   self.repo_name)
+                                   safe_unicode(self.repo_name))
 
     @hybrid_property
     def locked(self):
--- a/rhodecode/lib/dbmigrate/schema/db_1_6_0.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/schema/db_1_6_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.db_1_6_0
-    ~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Database Models for RhodeCode <=1.5.X
-
-    :created_on: Apr 08, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db_1_6_0
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode <=1.5.X
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import os
 import logging
@@ -770,7 +770,7 @@
 
     def __unicode__(self):
         return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   self.repo_name)
+                                   safe_unicode(self.repo_name))
 
     @hybrid_property
     def locked(self):
--- a/rhodecode/lib/dbmigrate/schema/db_1_7_0.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/schema/db_1_7_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.db
-    ~~~~~~~~~~~~~~~~~~
-
-    Database Models for RhodeCode
-
-    :created_on: Apr 08, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db
+~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import os
 import time
@@ -799,7 +799,7 @@
 
     def __unicode__(self):
         return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   self.repo_name)
+                                   safe_unicode(self.repo_name))
 
     @hybrid_property
     def locked(self):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/schema/db_1_8_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,2270 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db
+~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import os
+import time
+import logging
+import datetime
+import traceback
+import hashlib
+import collections
+
+from sqlalchemy import *
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
+from sqlalchemy.exc import DatabaseError
+from beaker.cache import cache_region, region_invalidate
+from webob.exc import HTTPNotFound
+
+from pylons.i18n.translation import lazy_ugettext as _
+
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
+
+from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
+    safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
+from rhodecode.lib.compat import json
+from rhodecode.lib.caching_query import FromCache
+
+from rhodecode.model.meta import Base, Session
+
+URL_SEP = '/'
+log = logging.getLogger(__name__)
+
+#==============================================================================
+# BASE CLASSES
+#==============================================================================
+
+_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
+
+
+class BaseModel(object):
+    """
+    Base Model for all classess
+    """
+
+    @classmethod
+    def _get_keys(cls):
+        """return column names for this model """
+        return class_mapper(cls).c.keys()
+
+    def get_dict(self):
+        """
+        return dict with keys and values corresponding
+        to this model data """
+
+        d = {}
+        for k in self._get_keys():
+            d[k] = getattr(self, k)
+
+        # also use __json__() if present to get additional fields
+        _json_attr = getattr(self, '__json__', None)
+        if _json_attr:
+            # update with attributes from __json__
+            if callable(_json_attr):
+                _json_attr = _json_attr()
+            for k, val in _json_attr.iteritems():
+                d[k] = val
+        return d
+
+    def get_appstruct(self):
+        """return list with keys and values tupples corresponding
+        to this model data """
+
+        l = []
+        for k in self._get_keys():
+            l.append((k, getattr(self, k),))
+        return l
+
+    def populate_obj(self, populate_dict):
+        """populate model with data from given populate_dict"""
+
+        for k in self._get_keys():
+            if k in populate_dict:
+                setattr(self, k, populate_dict[k])
+
+    @classmethod
+    def query(cls):
+        return Session().query(cls)
+
+    @classmethod
+    def get(cls, id_):
+        if id_:
+            return cls.query().get(id_)
+
+    @classmethod
+    def get_or_404(cls, id_):
+        try:
+            id_ = int(id_)
+        except (TypeError, ValueError):
+            raise HTTPNotFound
+
+        res = cls.query().get(id_)
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def getAll(cls):
+        # deprecated and left for backward compatibility
+        return cls.get_all()
+
+    @classmethod
+    def get_all(cls):
+        return cls.query().all()
+
+    @classmethod
+    def delete(cls, id_):
+        obj = cls.query().get(id_)
+        Session().delete(obj)
+
+    def __repr__(self):
+        if hasattr(self, '__unicode__'):
+            # python repr needs to return str
+            return safe_str(self.__unicode__())
+        return '<DB:%s>' % (self.__class__.__name__)
+
+
+class RhodeCodeSetting(Base, BaseModel):
+    __tablename__ = 'rhodecode_settings'
+    __table_args__ = (
+        UniqueConstraint('app_settings_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __init__(self, key='', val='', type='unicode'):
+        self.app_settings_name = key
+        self.app_settings_value = val
+        self.app_settings_type = type
+
+    @validates('_app_settings_value')
+    def validate_settings_value(self, key, val):
+        assert type(val) == unicode
+        return val
+
+    @hybrid_property
+    def app_settings_value(self):
+        v = self._app_settings_value
+        if self.app_settings_name in ["ldap_active",
+                                      "default_repo_enable_statistics",
+                                      "default_repo_enable_locking",
+                                      "default_repo_private",
+                                      "default_repo_enable_downloads"]:
+            v = str2bool(v)
+        return v
+
+    @app_settings_value.setter
+    def app_settings_value(self, val):
+        """
+        Setter that will always make sure we use unicode in app_settings_value
+
+        :param val:
+        """
+        self._app_settings_value = safe_unicode(val)
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__,
+            self.app_settings_name, self.app_settings_value
+        )
+
+    @classmethod
+    def get_by_name(cls, key):
+        return cls.query()\
+            .filter(cls.app_settings_name == key).scalar()
+
+    @classmethod
+    def get_by_name_or_create(cls, key):
+        res = cls.get_by_name(key)
+        if not res:
+            res = cls(key)
+        return res
+
+    @classmethod
+    def get_app_settings(cls, cache=False):
+
+        ret = cls.query()
+
+        if cache:
+            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
+
+        if not ret:
+            raise Exception('Could not get application settings !')
+        settings = {}
+        for each in ret:
+            settings['rhodecode_' + each.app_settings_name] = \
+                each.app_settings_value
+
+        return settings
+
+    @classmethod
+    def get_ldap_settings(cls, cache=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('ldap_')).all()
+        fd = {}
+        for row in ret:
+            fd.update({row.app_settings_name: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('default_')).all()
+        fd = {}
+        for row in ret:
+            key = row.app_settings_name
+            if strip_prefix:
+                key = remove_prefix(key, prefix='default_')
+            fd.update({key: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_server_info(cls):
+        import pkg_resources
+        import platform
+        import rhodecode
+        from rhodecode.lib.utils import check_git_version
+        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
+        mods += [('git', str(check_git_version()))]
+        info = {
+            'modules': sorted(mods, key=lambda k: k[0].lower()),
+            'py_version': platform.python_version(),
+            'platform': platform.platform(),
+            'rhodecode_version': rhodecode.__version__
+        }
+        return info
+
+
+class RhodeCodeUi(Base, BaseModel):
+    __tablename__ = 'rhodecode_ui'
+    __table_args__ = (
+        UniqueConstraint('ui_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    HOOK_UPDATE = 'changegroup.update'
+    HOOK_REPO_SIZE = 'changegroup.repo_size'
+    HOOK_PUSH = 'changegroup.push_logger'
+    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
+    HOOK_PULL = 'outgoing.pull_logger'
+    HOOK_PRE_PULL = 'preoutgoing.pre_pull'
+
+    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
+
+    # def __init__(self, section='', key='', value=''):
+    #     self.ui_section = section
+    #     self.ui_key = key
+    #     self.ui_value = value
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.ui_key == key).scalar()
+
+    @classmethod
+    def get_builtin_hooks(cls):
+        q = cls.query()
+        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        return q.all()
+
+    @classmethod
+    def get_custom_hooks(cls):
+        q = cls.query()
+        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        q = q.filter(cls.ui_section == 'hooks')
+        return q.all()
+
+    @classmethod
+    def get_repos_location(cls):
+        return cls.get_by_key('/').ui_value
+
+    @classmethod
+    def create_or_update_hook(cls, key, val):
+        new_ui = cls.get_by_key(key) or cls()
+        new_ui.ui_section = 'hooks'
+        new_ui.ui_active = True
+        new_ui.ui_key = key
+        new_ui.ui_value = val
+
+        Session().add(new_ui)
+
+    def __repr__(self):
+        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
+                                   self.ui_value)
+
+
+class User(Base, BaseModel):
+    __tablename__ = 'users'
+    __table_args__ = (
+        UniqueConstraint('username'), UniqueConstraint('email'),
+        Index('u_username_idx', 'username'),
+        Index('u_email_idx', 'email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    DEFAULT_USER = 'default'
+
+    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
+    ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+
+    user_log = relationship('UserLog')
+    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
+
+    repositories = relationship('Repository')
+    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
+    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
+
+    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
+
+    group_member = relationship('UserGroupMember', cascade='all')
+
+    notifications = relationship('UserNotification', cascade='all')
+    # notifications assigned to this user
+    user_created_notifications = relationship('Notification', cascade='all')
+    # comments created by this user
+    user_comments = relationship('ChangesetComment', cascade='all')
+    #extra emails for this user
+    user_emails = relationship('UserEmailMap', cascade='all')
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+    @property
+    def firstname(self):
+        # alias for future
+        return self.name
+
+    @property
+    def emails(self):
+        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
+        return [self.email] + [x.email for x in other]
+
+    @property
+    def ip_addresses(self):
+        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
+        return [x.ip_addr for x in ret]
+
+    @property
+    def username_and_name(self):
+        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
+
+    @property
+    def full_name(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def full_name_or_username(self):
+        return ('%s %s' % (self.firstname, self.lastname)
+                if (self.firstname and self.lastname) else self.username)
+
+    @property
+    def full_contact(self):
+        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
+
+    @property
+    def short_contact(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def is_admin(self):
+        return self.admin
+
+    @property
+    def AuthUser(self):
+        """
+        Returns instance of AuthUser for this user
+        """
+        from rhodecode.lib.auth import AuthUser
+        return AuthUser(user_id=self.user_id, api_key=self.api_key,
+                        username=self.username)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                     self.user_id, self.username)
+
+    @classmethod
+    def get_by_username(cls, username, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.username.ilike(username))
+        else:
+            q = cls.query().filter(cls.username == username)
+
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(username)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get_by_api_key(cls, api_key, cache=False):
+        q = cls.query().filter(cls.api_key == api_key)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_api_key_%s" % api_key))
+        return q.scalar()
+
+    @classmethod
+    def get_by_email(cls, email, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.email.ilike(email))
+        else:
+            q = cls.query().filter(cls.email == email)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_email_key_%s" % email))
+
+        ret = q.scalar()
+        if ret is None:
+            q = UserEmailMap.query()
+            # try fetching in alternate email map
+            if case_insensitive:
+                q = q.filter(UserEmailMap.email.ilike(email))
+            else:
+                q = q.filter(UserEmailMap.email == email)
+            q = q.options(joinedload(UserEmailMap.user))
+            if cache:
+                q = q.options(FromCache("sql_cache_short",
+                                        "get_email_map_key_%s" % email))
+            ret = getattr(q.scalar(), 'user', None)
+
+        return ret
+
+    @classmethod
+    def get_from_cs_author(cls, author):
+        """
+        Tries to get User objects out of commit author string
+
+        :param author:
+        """
+        from rhodecode.lib.helpers import email, author_name
+        # Valid email in the attribute passed, see if they're in the system
+        _email = email(author)
+        if _email:
+            user = cls.get_by_email(_email, case_insensitive=True)
+            if user:
+                return user
+        # Maybe we can match by username?
+        _author = author_name(author)
+        user = cls.get_by_username(_author, case_insensitive=True)
+        if user:
+            return user
+
+    def update_lastlogin(self):
+        """Update user lastlogin"""
+        self.last_login = datetime.datetime.now()
+        Session().add(self)
+        log.debug('updated user %s lastlogin' % self.username)
+
+    @classmethod
+    def get_first_admin(cls):
+        user = User.query().filter(User.admin == True).first()
+        if user is None:
+            raise Exception('Missing administrative account!')
+        return user
+
+    @classmethod
+    def get_default_user(cls, cache=False):
+        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
+        if user is None:
+            raise Exception('Missing default account!')
+        return user
+
+    def get_api_data(self):
+        """
+        Common function for generating user related data for API
+        """
+        user = self
+        data = dict(
+            user_id=user.user_id,
+            username=user.username,
+            firstname=user.name,
+            lastname=user.lastname,
+            email=user.email,
+            emails=user.emails,
+            api_key=user.api_key,
+            active=user.active,
+            admin=user.admin,
+            ldap_dn=user.ldap_dn,
+            last_login=user.last_login,
+            ip_addresses=user.ip_addresses
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+            full_name=self.full_name,
+            full_name_or_username=self.full_name_or_username,
+            short_contact=self.short_contact,
+            full_contact=self.full_contact
+        )
+        data.update(self.get_api_data())
+        return data
+
+
+class UserEmailMap(Base, BaseModel):
+    __tablename__ = 'user_email_map'
+    __table_args__ = (
+        Index('uem_email_idx', 'email'),
+        UniqueConstraint('email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    user = relationship('User', lazy='joined')
+
+    @validates('_email')
+    def validate_email(self, key, email):
+        # check if this email is not main one
+        main_email = Session().query(User).filter(User.email == email).scalar()
+        if main_email is not None:
+            raise AttributeError('email %s is present is user table' % email)
+        return email
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+
+class UserIpMap(Base, BaseModel):
+    __tablename__ = 'user_ip_map'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'ip_addr'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    user = relationship('User', lazy='joined')
+
+    @classmethod
+    def _get_ip_range(cls, ip_addr):
+        from rhodecode.lib import ipaddr
+        net = ipaddr.IPNetwork(address=ip_addr)
+        return [str(net.network), str(net.broadcast)]
+
+    def __json__(self):
+        return dict(
+          ip_addr=self.ip_addr,
+          ip_range=self._get_ip_range(self.ip_addr)
+        )
+
+
+class UserLog(Base, BaseModel):
+    __tablename__ = 'user_logs'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.repository_name,
+                                      self.action)
+
+    @property
+    def action_as_day(self):
+        return datetime.date(*self.action_date.timetuple()[:3])
+
+    user = relationship('User')
+    repository = relationship('Repository', cascade='')
+
+
+class UserGroup(Base, BaseModel):
+    __tablename__ = 'users_groups'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+
+    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
+    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
+    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
+    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
+
+    user = relationship('User')
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.users_group_id,
+                                      self.users_group_name)
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False,
+                          case_insensitive=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.users_group_name.ilike(group_name))
+        else:
+            q = cls.query().filter(cls.users_group_name == group_name)
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(group_name)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get(cls, user_group_id, cache=False):
+        user_group = cls.query()
+        if cache:
+            user_group = user_group.options(FromCache("sql_cache_short",
+                                    "get_users_group_%s" % user_group_id))
+        return user_group.get(user_group_id)
+
+    def get_api_data(self, with_members=True):
+        user_group = self
+
+        data = dict(
+            users_group_id=user_group.users_group_id,
+            group_name=user_group.users_group_name,
+            active=user_group.users_group_active,
+            owner=user_group.user.username,
+        )
+        if with_members:
+            members = []
+            for user in user_group.members:
+                user = user.user
+                members.append(user.get_api_data())
+            data['members'] = members
+
+        return data
+
+
+class UserGroupMember(Base, BaseModel):
+    __tablename__ = 'users_groups_members'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User', lazy='joined')
+    users_group = relationship('UserGroup')
+
+    def __init__(self, gr_id='', u_id=''):
+        self.users_group_id = gr_id
+        self.user_id = u_id
+
+
+class RepositoryField(Base, BaseModel):
+    __tablename__ = 'repositories_fields'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PREFIX = 'ex_'  # prefix used in form to not conflict with already existing fields
+
+    repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
+    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_type = Column("field_type", String(256), nullable=False, unique=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repository = relationship('Repository')
+
+    @property
+    def field_key_prefixed(self):
+        return 'ex_%s' % self.field_key
+
+    @classmethod
+    def un_prefix_key(cls, key):
+        if key.startswith(cls.PREFIX):
+            return key[len(cls.PREFIX):]
+        return key
+
+    @classmethod
+    def get_by_key_name(cls, key, repo):
+        row = cls.query()\
+                .filter(cls.repository == repo)\
+                .filter(cls.field_key == key).scalar()
+        return row
+
+
+class Repository(Base, BaseModel):
+    __tablename__ = 'repositories'
+    __table_args__ = (
+        UniqueConstraint('repo_name'),
+        Index('r_repo_name_idx', 'repo_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
+    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
+    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
+    description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
+
+    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
+
+    user = relationship('User')
+    fork = relationship('Repository', remote_side=repo_id)
+    group = relationship('RepoGroup')
+    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    stats = relationship('Statistics', cascade='all', uselist=False)
+
+    followers = relationship('UserFollowing',
+                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
+                             cascade='all')
+    extra_fields = relationship('RepositoryField',
+                                cascade="all, delete, delete-orphan")
+
+    logs = relationship('UserLog')
+    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
+
+    pull_requests_org = relationship('PullRequest',
+                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    pull_requests_other = relationship('PullRequest',
+                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
+                                   safe_unicode(self.repo_name))
+
+    @hybrid_property
+    def locked(self):
+        # always should return [user_id, timelocked]
+        if self._locked:
+            _lock_info = self._locked.split(':')
+            return int(_lock_info[0]), _lock_info[1]
+        return [None, None]
+
+    @locked.setter
+    def locked(self, val):
+        if val and isinstance(val, (list, tuple)):
+            self._locked = ':'.join(map(str, val))
+        else:
+            self._locked = None
+
+    @hybrid_property
+    def changeset_cache(self):
+        from rhodecode.lib.vcs.backends.base import EmptyChangeset
+        dummy = EmptyChangeset().__json__()
+        if not self._changeset_cache:
+            return dummy
+        try:
+            return json.loads(self._changeset_cache)
+        except TypeError:
+            return dummy
+
+    @changeset_cache.setter
+    def changeset_cache(self, val):
+        try:
+            self._changeset_cache = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def normalize_repo_name(cls, repo_name):
+        """
+        Normalizes os specific repo_name to the format internally stored inside
+        dabatabase using URL_SEP
+
+        :param cls:
+        :param repo_name:
+        """
+        return cls.url_sep().join(repo_name.split(os.sep))
+
+    @classmethod
+    def get_by_repo_name(cls, repo_name):
+        q = Session().query(cls).filter(cls.repo_name == repo_name)
+        q = q.options(joinedload(Repository.fork))\
+                .options(joinedload(Repository.user))\
+                .options(joinedload(Repository.group))
+        return q.scalar()
+
+    @classmethod
+    def get_by_full_path(cls, repo_full_path):
+        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
+        repo_name = cls.normalize_repo_name(repo_name)
+        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
+
+    @classmethod
+    def get_repo_forks(cls, repo_id):
+        return cls.query().filter(Repository.fork_id == repo_id)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all repos are stored
+
+        :param cls:
+        """
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == cls.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def forks(self):
+        """
+        Return forks of this repo
+        """
+        return Repository.get_repo_forks(self.repo_id)
+
+    @property
+    def parent(self):
+        """
+        Returns fork parent
+        """
+        return self.fork
+
+    @property
+    def just_name(self):
+        return self.repo_name.split(Repository.url_sep())[-1]
+
+    @property
+    def groups_with_parents(self):
+        groups = []
+        if self.group is None:
+            return groups
+
+        cur_gr = self.group
+        groups.insert(0, cur_gr)
+        while 1:
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            groups.insert(0, gr)
+
+        return groups
+
+    @property
+    def groups_and_repo(self):
+        return self.groups_with_parents, self.just_name, self.repo_name
+
+    @LazyProperty
+    def repo_path(self):
+        """
+        Returns base full path for that repository means where it actually
+        exists on a filesystem
+        """
+        q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+                                              Repository.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def repo_full_path(self):
+        p = [self.repo_path]
+        # we need to split the name by / since this is how we store the
+        # names in the database, but that eventually needs to be converted
+        # into a valid system path
+        p += self.repo_name.split(Repository.url_sep())
+        return os.path.join(*map(safe_unicode, p))
+
+    @property
+    def cache_keys(self):
+        """
+        Returns associated cache keys for that repo
+        """
+        return CacheInvalidation.query()\
+            .filter(CacheInvalidation.cache_args == self.repo_name)\
+            .order_by(CacheInvalidation.cache_key)\
+            .all()
+
+    def get_new_name(self, repo_name):
+        """
+        returns new full repository name based on assigned group and new new
+
+        :param group_name:
+        """
+        path_prefix = self.group.full_path_splitted if self.group else []
+        return Repository.url_sep().join(path_prefix + [repo_name])
+
+    @property
+    def _ui(self):
+        """
+        Creates an db based ui object for this repository
+        """
+        from rhodecode.lib.utils import make_ui
+        return make_ui('db', clear_session=False)
+
+    @classmethod
+    def is_valid(cls, repo_name):
+        """
+        returns True if given repo name is a valid filesystem repository
+
+        :param cls:
+        :param repo_name:
+        """
+        from rhodecode.lib.utils import is_valid_repo
+
+        return is_valid_repo(repo_name, cls.base_path())
+
+    def get_api_data(self):
+        """
+        Common function for generating repo api data
+
+        """
+        repo = self
+        data = dict(
+            repo_id=repo.repo_id,
+            repo_name=repo.repo_name,
+            repo_type=repo.repo_type,
+            clone_uri=repo.clone_uri,
+            private=repo.private,
+            created_on=repo.created_on,
+            description=repo.description,
+            landing_rev=repo.landing_rev,
+            owner=repo.user.username,
+            fork_of=repo.fork.repo_name if repo.fork else None,
+            enable_statistics=repo.enable_statistics,
+            enable_locking=repo.enable_locking,
+            enable_downloads=repo.enable_downloads,
+            last_changeset=repo.changeset_cache,
+            locked_by=User.get(self.locked[0]).get_api_data() \
+                if self.locked[0] else None,
+            locked_date=time_to_datetime(self.locked[1]) \
+                if self.locked[1] else None
+        )
+        rc_config = RhodeCodeSetting.get_app_settings()
+        repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
+        if repository_fields:
+            for f in self.extra_fields:
+                data[f.field_key_prefixed] = f.field_value
+
+        return data
+
+    @classmethod
+    def lock(cls, repo, user_id, lock_time=None):
+        if not lock_time:
+            lock_time = time.time()
+        repo.locked = [user_id, lock_time]
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def unlock(cls, repo):
+        repo.locked = None
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def getlock(cls, repo):
+        return repo.locked
+
+    @property
+    def last_db_change(self):
+        return self.updated_on
+
+    def clone_url(self, **override):
+        from pylons import url
+        from urlparse import urlparse
+        import urllib
+        parsed_url = urlparse(url('home', qualified=True))
+        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
+        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
+        args = {
+           'user': '',
+           'pass': '',
+           'scheme': parsed_url.scheme,
+           'netloc': parsed_url.netloc,
+           'prefix': decoded_path,
+           'path': self.repo_name
+        }
+
+        args.update(override)
+        return default_clone_uri % args
+
+    #==========================================================================
+    # SCM PROPERTIES
+    #==========================================================================
+
+    def get_changeset(self, rev=None):
+        return get_changeset_safe(self.scm_instance, rev)
+
+    def get_landing_changeset(self):
+        """
+        Returns landing changeset, or if that doesn't exist returns the tip
+        """
+        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
+        return cs
+
+    def update_changeset_cache(self, cs_cache=None):
+        """
+        Update cache of last changeset for repository, keys should be::
+
+            short_id
+            raw_id
+            revision
+            message
+            date
+            author
+
+        :param cs_cache:
+        """
+        from rhodecode.lib.vcs.backends.base import BaseChangeset
+        if cs_cache is None:
+            cs_cache = EmptyChangeset()
+            # use no-cache version here
+            scm_repo = self.scm_instance_no_cache()
+            if scm_repo:
+                cs_cache = scm_repo.get_changeset()
+
+        if isinstance(cs_cache, BaseChangeset):
+            cs_cache = cs_cache.__json__()
+
+        if (cs_cache != self.changeset_cache or not self.changeset_cache):
+            _default = datetime.datetime.fromtimestamp(0)
+            last_change = cs_cache.get('date') or _default
+            log.debug('updated repo %s with new cs cache %s'
+                      % (self.repo_name, cs_cache))
+            self.updated_on = last_change
+            self.changeset_cache = cs_cache
+            Session().add(self)
+            Session().commit()
+        else:
+            log.debug('Skipping repo:%s already with latest changes'
+                      % self.repo_name)
+
+    @property
+    def tip(self):
+        return self.get_changeset('tip')
+
+    @property
+    def author(self):
+        return self.tip.author
+
+    @property
+    def last_change(self):
+        return self.scm_instance.last_change
+
+    def get_comments(self, revisions=None):
+        """
+        Returns comments for this repository grouped by revisions
+
+        :param revisions: filter query by revisions only
+        """
+        cmts = ChangesetComment.query()\
+            .filter(ChangesetComment.repo == self)
+        if revisions:
+            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
+        grouped = collections.defaultdict(list)
+        for cmt in cmts.all():
+            grouped[cmt.revision].append(cmt)
+        return grouped
+
+    def statuses(self, revisions=None):
+        """
+        Returns statuses for this repository
+
+        :param revisions: list of revisions to get statuses for
+        """
+
+        statuses = ChangesetStatus.query()\
+            .filter(ChangesetStatus.repo == self)\
+            .filter(ChangesetStatus.version == 0)
+        if revisions:
+            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
+        grouped = {}
+
+        #maybe we have open new pullrequest without a status ?
+        stat = ChangesetStatus.STATUS_UNDER_REVIEW
+        status_lbl = ChangesetStatus.get_status_lbl(stat)
+        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
+            for rev in pr.revisions:
+                pr_id = pr.pull_request_id
+                pr_repo = pr.other_repo.repo_name
+                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
+
+        for stat in statuses.all():
+            pr_id = pr_repo = None
+            if stat.pull_request:
+                pr_id = stat.pull_request.pull_request_id
+                pr_repo = stat.pull_request.other_repo.repo_name
+            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
+                                      pr_id, pr_repo]
+        return grouped
+
+    def _repo_size(self):
+        from rhodecode.lib import helpers as h
+        log.debug('calculating repository size...')
+        return h.format_byte_size(self.scm_instance.size)
+
+    #==========================================================================
+    # SCM CACHE INSTANCE
+    #==========================================================================
+
+    def set_invalidate(self):
+        """
+        Mark caches of this repo as invalid.
+        """
+        CacheInvalidation.set_invalidate(self.repo_name)
+
+    def scm_instance_no_cache(self):
+        return self.__get_instance()
+
+    @property
+    def scm_instance(self):
+        import rhodecode
+        full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
+        if full_cache:
+            return self.scm_instance_cached()
+        return self.__get_instance()
+
+    def scm_instance_cached(self, valid_cache_keys=None):
+        @cache_region('long_term')
+        def _c(repo_name):
+            return self.__get_instance()
+        rn = self.repo_name
+
+        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
+        if not valid:
+            log.debug('Cache for %s invalidated, getting new object' % (rn))
+            region_invalidate(_c, None, rn)
+        else:
+            log.debug('Getting obj for %s from cache' % (rn))
+        return _c(rn)
+
+    def __get_instance(self):
+        repo_full_path = self.repo_full_path
+        try:
+            alias = get_scm(repo_full_path)[0]
+            log.debug('Creating instance of %s repository from %s'
+                      % (alias, repo_full_path))
+            backend = get_backend(alias)
+        except VCSError:
+            log.error(traceback.format_exc())
+            log.error('Perhaps this repository is in db and not in '
+                      'filesystem run rescan repositories with '
+                      '"destroy old data " option from admin panel')
+            return
+
+        if alias == 'hg':
+
+            repo = backend(safe_str(repo_full_path), create=False,
+                           baseui=self._ui)
+            # skip hidden web repository
+            if repo._get_hidden():
+                return
+        else:
+            repo = backend(repo_full_path, create=False)
+
+        return repo
+
+
+class RepoGroup(Base, BaseModel):
+    __tablename__ = 'groups'
+    __table_args__ = (
+        UniqueConstraint('group_name', 'group_parent_id'),
+        CheckConstraint('group_id != group_parent_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    __mapper_args__ = {'order_by': 'group_name'}
+
+    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
+    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    parent_group = relationship('RepoGroup', remote_side=group_id)
+    user = relationship('User')
+
+    def __init__(self, group_name='', parent_group=None):
+        self.group_name = group_name
+        self.parent_group = parent_group
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
+                                      self.group_name)
+
+    @classmethod
+    def groups_choices(cls, groups=None, show_empty_group=True):
+        from webhelpers.html import literal as _literal
+        if not groups:
+            groups = cls.query().all()
+
+        repo_groups = []
+        if show_empty_group:
+            repo_groups = [('-1', u'-- %s --' % _('top level'))]
+        sep = ' &raquo; '
+        _name = lambda k: _literal(sep.join(k))
+
+        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
+                              for x in groups])
+
+        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
+        return repo_groups
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+        if case_insensitive:
+            gr = cls.query()\
+                .filter(cls.group_name.ilike(group_name))
+        else:
+            gr = cls.query()\
+                .filter(cls.group_name == group_name)
+        if cache:
+            gr = gr.options(FromCache(
+                            "sql_cache_short",
+                            "get_group_%s" % _hash_key(group_name)
+                            )
+            )
+        return gr.scalar()
+
+    @property
+    def parents(self):
+        parents_recursion_limit = 5
+        groups = []
+        if self.parent_group is None:
+            return groups
+        cur_gr = self.parent_group
+        groups.insert(0, cur_gr)
+        cnt = 0
+        while 1:
+            cnt += 1
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            if cnt == parents_recursion_limit:
+                # this will prevent accidental infinit loops
+                log.error('group nested more than %s' %
+                          parents_recursion_limit)
+                break
+
+            groups.insert(0, gr)
+        return groups
+
+    @property
+    def children(self):
+        return RepoGroup.query().filter(RepoGroup.parent_group == self)
+
+    @property
+    def name(self):
+        return self.group_name.split(RepoGroup.url_sep())[-1]
+
+    @property
+    def full_path(self):
+        return self.group_name
+
+    @property
+    def full_path_splitted(self):
+        return self.group_name.split(RepoGroup.url_sep())
+
+    @property
+    def repositories(self):
+        return Repository.query()\
+                .filter(Repository.group == self)\
+                .order_by(Repository.repo_name)
+
+    @property
+    def repositories_recursive_count(self):
+        cnt = self.repositories.count()
+
+        def children_count(group):
+            cnt = 0
+            for child in group.children:
+                cnt += child.repositories.count()
+                cnt += children_count(child)
+            return cnt
+
+        return cnt + children_count(self)
+
+    def _recursive_objects(self, include_repos=True):
+        all_ = []
+
+        def _get_members(root_gr):
+            if include_repos:
+                for r in root_gr.repositories:
+                    all_.append(r)
+            childs = root_gr.children.all()
+            if childs:
+                for gr in childs:
+                    all_.append(gr)
+                    _get_members(gr)
+
+        _get_members(self)
+        return [self] + all_
+
+    def recursive_groups_and_repos(self):
+        """
+        Recursive return all groups, with repositories in those groups
+        """
+        return self._recursive_objects()
+
+    def recursive_groups(self):
+        """
+        Returns all children groups for this group including children of children
+        """
+        return self._recursive_objects(include_repos=False)
+
+    def get_new_name(self, group_name):
+        """
+        returns new full group name based on parent and new name
+
+        :param group_name:
+        """
+        path_prefix = (self.parent_group.full_path_splitted if
+                       self.parent_group else [])
+        return RepoGroup.url_sep().join(path_prefix + [group_name])
+
+    def get_api_data(self):
+        """
+        Common function for generating api data
+
+        """
+        group = self
+        data = dict(
+            group_id=group.group_id,
+            group_name=group.group_name,
+            group_description=group.group_description,
+            parent_group=group.parent_group.group_name if group.parent_group else None,
+            repositories=[x.repo_name for x in group.repositories],
+            owner=group.user.username
+        )
+        return data
+
+
+class Permission(Base, BaseModel):
+    __tablename__ = 'permissions'
+    __table_args__ = (
+        Index('p_perm_name_idx', 'permission_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PERMS = [
+        ('hg.admin', _('RhodeCode Administrator')),
+
+        ('repository.none', _('Repository no access')),
+        ('repository.read', _('Repository read access')),
+        ('repository.write', _('Repository write access')),
+        ('repository.admin', _('Repository admin access')),
+
+        ('group.none', _('Repository group no access')),
+        ('group.read', _('Repository group read access')),
+        ('group.write', _('Repository group write access')),
+        ('group.admin', _('Repository group admin access')),
+
+        ('usergroup.none', _('User group no access')),
+        ('usergroup.read', _('User group read access')),
+        ('usergroup.write', _('User group write access')),
+        ('usergroup.admin', _('User group admin access')),
+
+        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
+        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
+
+        ('hg.usergroup.create.false', _('User Group creation disabled')),
+        ('hg.usergroup.create.true', _('User Group creation enabled')),
+
+        ('hg.create.none', _('Repository creation disabled')),
+        ('hg.create.repository', _('Repository creation enabled')),
+
+        ('hg.fork.none', _('Repository forking disabled')),
+        ('hg.fork.repository', _('Repository forking enabled')),
+
+        ('hg.register.none', _('Registration disabled')),
+        ('hg.register.manual_activate', _('User Registration with manual account activation')),
+        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
+
+        ('hg.extern_activate.manual', _('Manual activation of external account')),
+        ('hg.extern_activate.auto', _('Automatic activation of external account')),
+
+    ]
+
+    #definition of system default permissions for DEFAULT user
+    DEFAULT_USER_PERMISSIONS = [
+        'repository.read',
+        'group.read',
+        'usergroup.read',
+        'hg.create.repository',
+        'hg.fork.repository',
+        'hg.register.manual_activate',
+        'hg.extern_activate.auto',
+    ]
+
+    # defines which permissions are more important higher the more important
+    # Weight defines which permissions are more important.
+    # The higher number the more important.
+    PERM_WEIGHTS = {
+        'repository.none': 0,
+        'repository.read': 1,
+        'repository.write': 3,
+        'repository.admin': 4,
+
+        'group.none': 0,
+        'group.read': 1,
+        'group.write': 3,
+        'group.admin': 4,
+
+        'usergroup.none': 0,
+        'usergroup.read': 1,
+        'usergroup.write': 3,
+        'usergroup.admin': 4,
+        'hg.repogroup.create.false': 0,
+        'hg.repogroup.create.true': 1,
+
+        'hg.usergroup.create.false': 0,
+        'hg.usergroup.create.true': 1,
+
+        'hg.fork.none': 0,
+        'hg.fork.repository': 1,
+        'hg.create.none': 0,
+        'hg.create.repository': 1
+    }
+
+    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__, self.permission_id, self.permission_name
+        )
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.permission_name == key).scalar()
+
+    @classmethod
+    def get_default_perms(cls, default_user_id):
+        q = Session().query(UserRepoToPerm, Repository, cls)\
+         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
+         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_group_perms(cls, default_user_id):
+        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
+         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
+         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_user_group_perms(cls, default_user_id):
+        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
+         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
+         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserUserGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+
+class UserRepoToPerm(Base, BaseModel):
+    __tablename__ = 'repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    repository = relationship('Repository')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, repository, permission):
+        n = cls()
+        n.user = user
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.repository)
+
+
+class UserUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    user_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, user_group, permission):
+        n = cls()
+        n.user = user
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.user_group)
+
+
+class UserToPerm(Base, BaseModel):
+    __tablename__ = 'user_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    permission = relationship('Permission', lazy='joined')
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.permission)
+
+
+class UserGroupRepoToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    repository = relationship('Repository')
+
+    @classmethod
+    def create(cls, users_group, repository, permission):
+        n = cls()
+        n.users_group = users_group
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
+
+
+class UserGroupUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_group_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
+        CheckConstraint('target_user_group_id != user_group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
+    user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, target_user_group, user_group, permission):
+        n = cls()
+        n.target_user_group = target_user_group
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
+
+
+class UserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'permission_id',),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+
+class UserRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    group = relationship('RepoGroup')
+    permission = relationship('Permission')
+
+
+class UserGroupRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    group = relationship('RepoGroup')
+
+
+class Statistics(Base, BaseModel):
+    __tablename__ = 'statistics'
+    __table_args__ = (
+         UniqueConstraint('repository_id'),
+         {'extend_existing': True, 'mysql_engine': 'InnoDB',
+          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
+    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
+    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
+    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
+
+    repository = relationship('Repository', single_parent=True)
+
+
+class UserFollowing(Base, BaseModel):
+    __tablename__ = 'user_followings'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'follows_repository_id'),
+        UniqueConstraint('user_id', 'follows_user_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
+    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+
+    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
+
+    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
+    follows_repository = relationship('Repository', order_by='Repository.repo_name')
+
+    @classmethod
+    def get_repo_followers(cls, repo_id):
+        return cls.query().filter(cls.follows_repo_id == repo_id)
+
+
+class CacheInvalidation(Base, BaseModel):
+    __tablename__ = 'cache_invalidation'
+    __table_args__ = (
+        UniqueConstraint('cache_key'),
+        Index('key_idx', 'cache_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    # cache_id, not used
+    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    # cache_key as created by _get_cache_key
+    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    # cache_args is a repo_name
+    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    # instance sets cache_active True when it is caching,
+    # other instances set cache_active to False to indicate that this cache is invalid
+    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+
+    def __init__(self, cache_key, repo_name=''):
+        self.cache_key = cache_key
+        self.cache_args = repo_name
+        self.cache_active = False
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
+                            self.cache_id, self.cache_key, self.cache_active)
+
+    def _cache_key_partition(self):
+        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
+        return prefix, repo_name, suffix
+
+    def get_prefix(self):
+        """
+        get prefix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[0]
+
+    def get_suffix(self):
+        """
+        get suffix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[2]
+
+    @classmethod
+    def clear_cache(cls):
+        """
+        Delete all cache keys from database.
+        Should only be run when all instances are down and all entries thus stale.
+        """
+        cls.query().delete()
+        Session().commit()
+
+    @classmethod
+    def _get_cache_key(cls, key):
+        """
+        Wrapper for generating a unique cache key for this instance and "key".
+        key must / will start with a repo_name which will be stored in .cache_args .
+        """
+        import rhodecode
+        prefix = rhodecode.CONFIG.get('instance_id', '')
+        return "%s%s" % (prefix, key)
+
+    @classmethod
+    def set_invalidate(cls, repo_name):
+        """
+        Mark all caches of a repo as invalid in the database.
+        """
+        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
+
+        try:
+            for inv_obj in inv_objs:
+                log.debug('marking %s key for invalidation based on repo_name=%s'
+                          % (inv_obj, safe_str(repo_name)))
+                inv_obj.cache_active = False
+                Session().add(inv_obj)
+            Session().commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+
+    @classmethod
+    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
+        """
+        Mark this cache key as active and currently cached.
+        Return True if the existing cache registration still was valid.
+        Return False to indicate that it had been invalidated and caches should be refreshed.
+        """
+
+        key = (repo_name + '_' + kind) if kind else repo_name
+        cache_key = cls._get_cache_key(key)
+
+        if valid_cache_keys and cache_key in valid_cache_keys:
+            return True
+
+        try:
+            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
+            if not inv_obj:
+                inv_obj = CacheInvalidation(cache_key, repo_name)
+            was_valid = inv_obj.cache_active
+            inv_obj.cache_active = True
+            Session().add(inv_obj)
+            Session().commit()
+            return was_valid
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+            return False
+
+    @classmethod
+    def get_valid_cache_keys(cls):
+        """
+        Return opaque object with information of which caches still are valid
+        and can be used without checking for invalidation.
+        """
+        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
+
+
+class ChangesetComment(Base, BaseModel):
+    __tablename__ = 'changeset_comments'
+    __table_args__ = (
+        Index('cc_revision_idx', 'revision'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    revision = Column('revision', String(40), nullable=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+    line_no = Column('line_no', Unicode(10), nullable=True)
+    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
+    f_path = Column('f_path', Unicode(1000), nullable=True)
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+    text = Column('text', UnicodeText(25000), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    @classmethod
+    def get_users(cls, revision=None, pull_request_id=None):
+        """
+        Returns user associated with this ChangesetComment. ie those
+        who actually commented
+
+        :param cls:
+        :param revision:
+        """
+        q = Session().query(User)\
+                .join(ChangesetComment.author)
+        if revision:
+            q = q.filter(cls.revision == revision)
+        elif pull_request_id:
+            q = q.filter(cls.pull_request_id == pull_request_id)
+        return q.all()
+
+
+class ChangesetStatus(Base, BaseModel):
+    __tablename__ = 'changeset_statuses'
+    __table_args__ = (
+        Index('cs_revision_idx', 'revision'),
+        Index('cs_version_idx', 'version'),
+        UniqueConstraint('repo_id', 'revision', 'version'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
+    STATUS_APPROVED = 'approved'
+    STATUS_REJECTED = 'rejected'
+    STATUS_UNDER_REVIEW = 'under_review'
+
+    STATUSES = [
+        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
+        (STATUS_APPROVED, _("Approved")),
+        (STATUS_REJECTED, _("Rejected")),
+        (STATUS_UNDER_REVIEW, _("Under Review")),
+    ]
+
+    changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    revision = Column('revision', String(40), nullable=False)
+    status = Column('status', String(128), nullable=False, default=DEFAULT)
+    changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
+    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
+    version = Column('version', Integer(), nullable=False, default=0)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    comment = relationship('ChangesetComment', lazy='joined')
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__,
+            self.status, self.author
+        )
+
+    @classmethod
+    def get_status_lbl(cls, value):
+        return dict(cls.STATUSES).get(value)
+
+    @property
+    def status_lbl(self):
+        return ChangesetStatus.get_status_lbl(self.status)
+
+
+class PullRequest(Base, BaseModel):
+    __tablename__ = 'pull_requests'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    # values for .status
+    STATUS_NEW = u'new'
+    STATUS_OPEN = u'open'
+    STATUS_CLOSED = u'closed'
+
+    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
+    title = Column('title', Unicode(256), nullable=True)
+    description = Column('description', UnicodeText(10240), nullable=True)
+    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    _revisions = Column('revisions', UnicodeText(20500))  # 500 revisions max
+    org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    org_ref = Column('org_ref', Unicode(256), nullable=False)
+    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    other_ref = Column('other_ref', Unicode(256), nullable=False)
+
+    @hybrid_property
+    def revisions(self):
+        return self._revisions.split(':')
+
+    @revisions.setter
+    def revisions(self, val):
+        self._revisions = ':'.join(val)
+
+    @property
+    def org_ref_parts(self):
+        return self.org_ref.split(':')
+
+    @property
+    def other_ref_parts(self):
+        return self.other_ref.split(':')
+
+    author = relationship('User', lazy='joined')
+    reviewers = relationship('PullRequestReviewers',
+                             cascade="all, delete, delete-orphan")
+    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
+    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
+    statuses = relationship('ChangesetStatus')
+    comments = relationship('ChangesetComment',
+                             cascade="all, delete, delete-orphan")
+
+    def is_closed(self):
+        return self.status == self.STATUS_CLOSED
+
+    @property
+    def last_review_status(self):
+        return self.statuses[-1].status if self.statuses else ''
+
+    def __json__(self):
+        return dict(
+            revisions=self.revisions
+        )
+
+
+class PullRequestReviewers(Base, BaseModel):
+    __tablename__ = 'pull_request_reviewers'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    def __init__(self, user=None, pull_request=None):
+        self.user = user
+        self.pull_request = pull_request
+
+    pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
+
+    user = relationship('User')
+    pull_request = relationship('PullRequest')
+
+
+class Notification(Base, BaseModel):
+    __tablename__ = 'notifications'
+    __table_args__ = (
+        Index('notification_type_idx', 'type'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    TYPE_CHANGESET_COMMENT = u'cs_comment'
+    TYPE_MESSAGE = u'message'
+    TYPE_MENTION = u'mention'
+    TYPE_REGISTRATION = u'registration'
+    TYPE_PULL_REQUEST = u'pull_request'
+    TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
+
+    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
+    subject = Column('subject', Unicode(512), nullable=True)
+    body = Column('body', UnicodeText(50000), nullable=True)
+    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    type_ = Column('type', Unicode(256))
+
+    created_by_user = relationship('User')
+    notifications_to_users = relationship('UserNotification', lazy='joined',
+                                          cascade="all, delete, delete-orphan")
+
+    @property
+    def recipients(self):
+        return [x.user for x in UserNotification.query()\
+                .filter(UserNotification.notification == self)\
+                .order_by(UserNotification.user_id.asc()).all()]
+
+    @classmethod
+    def create(cls, created_by, subject, body, recipients, type_=None):
+        if type_ is None:
+            type_ = Notification.TYPE_MESSAGE
+
+        notification = cls()
+        notification.created_by_user = created_by
+        notification.subject = subject
+        notification.body = body
+        notification.type_ = type_
+        notification.created_on = datetime.datetime.now()
+
+        for u in recipients:
+            assoc = UserNotification()
+            assoc.notification = notification
+            u.notifications.append(assoc)
+        Session().add(notification)
+        return notification
+
+    @property
+    def description(self):
+        from rhodecode.model.notification import NotificationModel
+        return NotificationModel().make_description(self)
+
+
+class UserNotification(Base, BaseModel):
+    __tablename__ = 'user_to_notification'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'notification_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
+    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
+    read = Column('read', Boolean, default=False)
+    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
+
+    user = relationship('User', lazy="joined")
+    notification = relationship('Notification', lazy="joined",
+                                order_by=lambda: Notification.created_on.desc(),)
+
+    def mark_as_read(self):
+        self.read = True
+        Session().add(self)
+
+
+class Gist(Base, BaseModel):
+    __tablename__ = 'gists'
+    __table_args__ = (
+        Index('g_gist_access_id_idx', 'gist_access_id'),
+        Index('g_created_on_idx', 'created_on'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    GIST_PUBLIC = u'public'
+    GIST_PRIVATE = u'private'
+
+    gist_id = Column('gist_id', Integer(), primary_key=True)
+    gist_access_id = Column('gist_access_id', Unicode(250))
+    gist_description = Column('gist_description', UnicodeText(1024))
+    gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
+    gist_expires = Column('gist_expires', Float(53), nullable=False)
+    gist_type = Column('gist_type', Unicode(128), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    owner = relationship('User')
+
+    @classmethod
+    def get_or_404(cls, id_):
+        res = cls.query().filter(cls.gist_access_id == id_).scalar()
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def get_by_access_id(cls, gist_access_id):
+        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
+
+    def gist_url(self):
+        import rhodecode
+        alias_url = rhodecode.CONFIG.get('gist_alias_url')
+        if alias_url:
+            return alias_url.replace('{gistid}', self.gist_access_id)
+
+        from pylons import url
+        return url('gist', gist_id=self.gist_access_id, qualified=True)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all gists are stored
+
+        :param cls:
+        """
+        from rhodecode.model.gist import GIST_STORE_LOC
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == URL_SEP)
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
+
+    def get_api_data(self):
+        """
+        Common function for generating gist related data for API
+        """
+        gist = self
+        data = dict(
+            gist_id=gist.gist_id,
+            type=gist.gist_type,
+            access_id=gist.gist_access_id,
+            description=gist.gist_description,
+            url=gist.gist_url(),
+            expires=gist.gist_expires,
+            created_on=gist.created_on,
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+        )
+        data.update(self.get_api_data())
+        return data
+    ## SCM functions
+
+    @property
+    def scm_instance(self):
+        from rhodecode.lib.vcs import get_repo
+        base_path = self.base_path()
+        return get_repo(os.path.join(*map(safe_str,
+                                          [base_path, self.gist_access_id])))
+
+
+class DbMigrateVersion(Base, BaseModel):
+    __tablename__ = 'db_migrate_version'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    repository_id = Column('repository_id', String(250), primary_key=True)
+    repository_path = Column('repository_path', Text)
+    version = Column('version', Integer)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/schema/db_2_0_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,2330 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db
+~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import os
+import time
+import logging
+import datetime
+import traceback
+import hashlib
+import collections
+import functools
+
+from sqlalchemy import *
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
+from sqlalchemy.exc import DatabaseError
+from beaker.cache import cache_region, region_invalidate
+from webob.exc import HTTPNotFound
+
+from pylons.i18n.translation import lazy_ugettext as _
+
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
+
+from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
+    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int
+from rhodecode.lib.compat import json
+from rhodecode.lib.caching_query import FromCache
+
+from rhodecode.model.meta import Base, Session
+
+URL_SEP = '/'
+log = logging.getLogger(__name__)
+
+#==============================================================================
+# BASE CLASSES
+#==============================================================================
+
+_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
+
+
+class BaseModel(object):
+    """
+    Base Model for all classess
+    """
+
+    @classmethod
+    def _get_keys(cls):
+        """return column names for this model """
+        return class_mapper(cls).c.keys()
+
+    def get_dict(self):
+        """
+        return dict with keys and values corresponding
+        to this model data """
+
+        d = {}
+        for k in self._get_keys():
+            d[k] = getattr(self, k)
+
+        # also use __json__() if present to get additional fields
+        _json_attr = getattr(self, '__json__', None)
+        if _json_attr:
+            # update with attributes from __json__
+            if callable(_json_attr):
+                _json_attr = _json_attr()
+            for k, val in _json_attr.iteritems():
+                d[k] = val
+        return d
+
+    def get_appstruct(self):
+        """return list with keys and values tupples corresponding
+        to this model data """
+
+        l = []
+        for k in self._get_keys():
+            l.append((k, getattr(self, k),))
+        return l
+
+    def populate_obj(self, populate_dict):
+        """populate model with data from given populate_dict"""
+
+        for k in self._get_keys():
+            if k in populate_dict:
+                setattr(self, k, populate_dict[k])
+
+    @classmethod
+    def query(cls):
+        return Session().query(cls)
+
+    @classmethod
+    def get(cls, id_):
+        if id_:
+            return cls.query().get(id_)
+
+    @classmethod
+    def get_or_404(cls, id_):
+        try:
+            id_ = int(id_)
+        except (TypeError, ValueError):
+            raise HTTPNotFound
+
+        res = cls.query().get(id_)
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def getAll(cls):
+        # deprecated and left for backward compatibility
+        return cls.get_all()
+
+    @classmethod
+    def get_all(cls):
+        return cls.query().all()
+
+    @classmethod
+    def delete(cls, id_):
+        obj = cls.query().get(id_)
+        Session().delete(obj)
+
+    def __repr__(self):
+        if hasattr(self, '__unicode__'):
+            # python repr needs to return str
+            return safe_str(self.__unicode__())
+        return '<DB:%s>' % (self.__class__.__name__)
+
+
+class RhodeCodeSetting(Base, BaseModel):
+    SETTINGS_TYPES = {
+        'str': safe_str,
+        'int': safe_int,
+        'unicode': safe_unicode,
+        'bool': str2bool,
+        'list': functools.partial(aslist, sep=',')
+    }
+    __tablename__ = 'rhodecode_settings'
+    __table_args__ = (
+        UniqueConstraint('app_settings_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __init__(self, key='', val='', type='unicode'):
+        self.app_settings_name = key
+        self.app_settings_value = val
+        self.app_settings_type = type
+
+    @validates('_app_settings_value')
+    def validate_settings_value(self, key, val):
+        assert type(val) == unicode
+        return val
+
+    @hybrid_property
+    def app_settings_value(self):
+        v = self._app_settings_value
+        _type = self.app_settings_type
+        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
+        return converter(v)
+
+    @app_settings_value.setter
+    def app_settings_value(self, val):
+        """
+        Setter that will always make sure we use unicode in app_settings_value
+
+        :param val:
+        """
+        self._app_settings_value = safe_unicode(val)
+
+    @hybrid_property
+    def app_settings_type(self):
+        return self._app_settings_type
+
+    @app_settings_type.setter
+    def app_settings_type(self, val):
+        if val not in self.SETTINGS_TYPES:
+            raise Exception('type must be one of %s got %s'
+                            % (self.SETTINGS_TYPES.keys(), val))
+        self._app_settings_type = val
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (
+            self.__class__.__name__,
+            self.app_settings_name, self.app_settings_value, self.app_settings_type
+        )
+
+    @classmethod
+    def get_by_name(cls, key):
+        return cls.query()\
+            .filter(cls.app_settings_name == key).scalar()
+
+    @classmethod
+    def get_by_name_or_create(cls, key, val='', type='unicode'):
+        res = cls.get_by_name(key)
+        if not res:
+            res = cls(key, val, type)
+        return res
+
+    @classmethod
+    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
+        """
+        Creates or updates RhodeCode setting. If updates is triggered it will only
+        update parameters that are explicityl set Optional instance will be skipped
+
+        :param key:
+        :param val:
+        :param type:
+        :return:
+        """
+        res = cls.get_by_name(key)
+        if not res:
+            val = Optional.extract(val)
+            type = Optional.extract(type)
+            res = cls(key, val, type)
+        else:
+            res.app_settings_name = key
+            if not isinstance(val, Optional):
+                # update if set
+                res.app_settings_value = val
+            if not isinstance(type, Optional):
+                # update if set
+                res.app_settings_type = type
+        return res
+
+    @classmethod
+    def get_app_settings(cls, cache=False):
+
+        ret = cls.query()
+
+        if cache:
+            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
+
+        if not ret:
+            raise Exception('Could not get application settings !')
+        settings = {}
+        for each in ret:
+            settings['rhodecode_' + each.app_settings_name] = \
+                each.app_settings_value
+
+        return settings
+
+    @classmethod
+    def get_auth_plugins(cls, cache=False):
+        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
+        return auth_plugins
+
+    @classmethod
+    def get_auth_settings(cls, cache=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('auth_')).all()
+        fd = {}
+        for row in ret:
+            fd.update({row.app_settings_name: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('default_')).all()
+        fd = {}
+        for row in ret:
+            key = row.app_settings_name
+            if strip_prefix:
+                key = remove_prefix(key, prefix='default_')
+            fd.update({key: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_server_info(cls):
+        import pkg_resources
+        import platform
+        import rhodecode
+        from rhodecode.lib.utils import check_git_version
+        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
+        info = {
+            'modules': sorted(mods, key=lambda k: k[0].lower()),
+            'py_version': platform.python_version(),
+            'platform': platform.platform(),
+            'rhodecode_version': rhodecode.__version__,
+            'git_version': str(check_git_version()),
+            'git_path': rhodecode.CONFIG.get('git_path')
+        }
+        return info
+
+
+class RhodeCodeUi(Base, BaseModel):
+    __tablename__ = 'rhodecode_ui'
+    __table_args__ = (
+        UniqueConstraint('ui_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    HOOK_UPDATE = 'changegroup.update'
+    HOOK_REPO_SIZE = 'changegroup.repo_size'
+    HOOK_PUSH = 'changegroup.push_logger'
+    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
+    HOOK_PULL = 'outgoing.pull_logger'
+    HOOK_PRE_PULL = 'preoutgoing.pre_pull'
+
+    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
+
+    # def __init__(self, section='', key='', value=''):
+    #     self.ui_section = section
+    #     self.ui_key = key
+    #     self.ui_value = value
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.ui_key == key).scalar()
+
+    @classmethod
+    def get_builtin_hooks(cls):
+        q = cls.query()
+        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        return q.all()
+
+    @classmethod
+    def get_custom_hooks(cls):
+        q = cls.query()
+        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        q = q.filter(cls.ui_section == 'hooks')
+        return q.all()
+
+    @classmethod
+    def get_repos_location(cls):
+        return cls.get_by_key('/').ui_value
+
+    @classmethod
+    def create_or_update_hook(cls, key, val):
+        new_ui = cls.get_by_key(key) or cls()
+        new_ui.ui_section = 'hooks'
+        new_ui.ui_active = True
+        new_ui.ui_key = key
+        new_ui.ui_value = val
+
+        Session().add(new_ui)
+
+    def __repr__(self):
+        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
+                                   self.ui_value)
+
+
+class User(Base, BaseModel):
+    __tablename__ = 'users'
+    __table_args__ = (
+        UniqueConstraint('username'), UniqueConstraint('email'),
+        Index('u_username_idx', 'username'),
+        Index('u_email_idx', 'email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    DEFAULT_USER = 'default'
+
+    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
+    extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    #for migration reasons, this is going to be later deleted
+    ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    user_log = relationship('UserLog')
+    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
+
+    repositories = relationship('Repository')
+    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
+    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
+
+    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
+
+    group_member = relationship('UserGroupMember', cascade='all')
+
+    notifications = relationship('UserNotification', cascade='all')
+    # notifications assigned to this user
+    user_created_notifications = relationship('Notification', cascade='all')
+    # comments created by this user
+    user_comments = relationship('ChangesetComment', cascade='all')
+    #extra emails for this user
+    user_emails = relationship('UserEmailMap', cascade='all')
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+    @property
+    def firstname(self):
+        # alias for future
+        return self.name
+
+    @property
+    def emails(self):
+        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
+        return [self.email] + [x.email for x in other]
+
+    @property
+    def ip_addresses(self):
+        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
+        return [x.ip_addr for x in ret]
+
+    @property
+    def username_and_name(self):
+        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
+
+    @property
+    def full_name(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def full_name_or_username(self):
+        return ('%s %s' % (self.firstname, self.lastname)
+                if (self.firstname and self.lastname) else self.username)
+
+    @property
+    def full_contact(self):
+        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
+
+    @property
+    def short_contact(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def is_admin(self):
+        return self.admin
+
+    @property
+    def AuthUser(self):
+        """
+        Returns instance of AuthUser for this user
+        """
+        from rhodecode.lib.auth import AuthUser
+        return AuthUser(user_id=self.user_id, api_key=self.api_key,
+                        username=self.username)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                     self.user_id, self.username)
+
+    @classmethod
+    def get_by_username(cls, username, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.username.ilike(username))
+        else:
+            q = cls.query().filter(cls.username == username)
+
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(username)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get_by_api_key(cls, api_key, cache=False):
+        q = cls.query().filter(cls.api_key == api_key)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_api_key_%s" % api_key))
+        return q.scalar()
+
+    @classmethod
+    def get_by_email(cls, email, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.email.ilike(email))
+        else:
+            q = cls.query().filter(cls.email == email)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_email_key_%s" % email))
+
+        ret = q.scalar()
+        if ret is None:
+            q = UserEmailMap.query()
+            # try fetching in alternate email map
+            if case_insensitive:
+                q = q.filter(UserEmailMap.email.ilike(email))
+            else:
+                q = q.filter(UserEmailMap.email == email)
+            q = q.options(joinedload(UserEmailMap.user))
+            if cache:
+                q = q.options(FromCache("sql_cache_short",
+                                        "get_email_map_key_%s" % email))
+            ret = getattr(q.scalar(), 'user', None)
+
+        return ret
+
+    @classmethod
+    def get_from_cs_author(cls, author):
+        """
+        Tries to get User objects out of commit author string
+
+        :param author:
+        """
+        from rhodecode.lib.helpers import email, author_name
+        # Valid email in the attribute passed, see if they're in the system
+        _email = email(author)
+        if _email:
+            user = cls.get_by_email(_email, case_insensitive=True)
+            if user:
+                return user
+        # Maybe we can match by username?
+        _author = author_name(author)
+        user = cls.get_by_username(_author, case_insensitive=True)
+        if user:
+            return user
+
+    def update_lastlogin(self):
+        """Update user lastlogin"""
+        self.last_login = datetime.datetime.now()
+        Session().add(self)
+        log.debug('updated user %s lastlogin' % self.username)
+
+    @classmethod
+    def get_first_admin(cls):
+        user = User.query().filter(User.admin == True).first()
+        if user is None:
+            raise Exception('Missing administrative account!')
+        return user
+
+    @classmethod
+    def get_default_user(cls, cache=False):
+        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
+        if user is None:
+            raise Exception('Missing default account!')
+        return user
+
+    def get_api_data(self):
+        """
+        Common function for generating user related data for API
+        """
+        user = self
+        data = dict(
+            user_id=user.user_id,
+            username=user.username,
+            firstname=user.name,
+            lastname=user.lastname,
+            email=user.email,
+            emails=user.emails,
+            api_key=user.api_key,
+            active=user.active,
+            admin=user.admin,
+            extern_type=user.extern_type,
+            extern_name=user.extern_name,
+            last_login=user.last_login,
+            ip_addresses=user.ip_addresses
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+            full_name=self.full_name,
+            full_name_or_username=self.full_name_or_username,
+            short_contact=self.short_contact,
+            full_contact=self.full_contact
+        )
+        data.update(self.get_api_data())
+        return data
+
+
+class UserEmailMap(Base, BaseModel):
+    __tablename__ = 'user_email_map'
+    __table_args__ = (
+        Index('uem_email_idx', 'email'),
+        UniqueConstraint('email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    user = relationship('User', lazy='joined')
+
+    @validates('_email')
+    def validate_email(self, key, email):
+        # check if this email is not main one
+        main_email = Session().query(User).filter(User.email == email).scalar()
+        if main_email is not None:
+            raise AttributeError('email %s is present is user table' % email)
+        return email
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+
+class UserIpMap(Base, BaseModel):
+    __tablename__ = 'user_ip_map'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'ip_addr'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    user = relationship('User', lazy='joined')
+
+    @classmethod
+    def _get_ip_range(cls, ip_addr):
+        from rhodecode.lib import ipaddr
+        net = ipaddr.IPNetwork(address=ip_addr)
+        return [str(net.network), str(net.broadcast)]
+
+    def __json__(self):
+        return dict(
+          ip_addr=self.ip_addr,
+          ip_range=self._get_ip_range(self.ip_addr)
+        )
+
+
+class UserLog(Base, BaseModel):
+    __tablename__ = 'user_logs'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.repository_name,
+                                      self.action)
+
+    @property
+    def action_as_day(self):
+        return datetime.date(*self.action_date.timetuple()[:3])
+
+    user = relationship('User')
+    repository = relationship('Repository', cascade='')
+
+
+class UserGroup(Base, BaseModel):
+    __tablename__ = 'users_groups'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    # don't trigger lazy load for migrations
+    #members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
+    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
+    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
+    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
+
+    user = relationship('User')
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.users_group_id,
+                                      self.users_group_name)
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False,
+                          case_insensitive=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.users_group_name.ilike(group_name))
+        else:
+            q = cls.query().filter(cls.users_group_name == group_name)
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(group_name)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get(cls, user_group_id, cache=False):
+        user_group = cls.query()
+        if cache:
+            user_group = user_group.options(FromCache("sql_cache_short",
+                                    "get_users_group_%s" % user_group_id))
+        return user_group.get(user_group_id)
+
+    def get_api_data(self, with_members=True):
+        user_group = self
+
+        data = dict(
+            users_group_id=user_group.users_group_id,
+            group_name=user_group.users_group_name,
+            group_description=user_group.user_group_description,
+            active=user_group.users_group_active,
+            owner=user_group.user.username,
+        )
+        if with_members:
+            members = []
+            for user in user_group.members:
+                user = user.user
+                members.append(user.get_api_data())
+            data['members'] = members
+
+        return data
+
+
+class UserGroupMember(Base, BaseModel):
+    __tablename__ = 'users_groups_members'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User', lazy='joined')
+    users_group = relationship('UserGroup')
+
+    def __init__(self, gr_id='', u_id=''):
+        self.users_group_id = gr_id
+        self.user_id = u_id
+
+
+class RepositoryField(Base, BaseModel):
+    __tablename__ = 'repositories_fields'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PREFIX = 'ex_'  # prefix used in form to not conflict with already existing fields
+
+    repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
+    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_type = Column("field_type", String(256), nullable=False, unique=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repository = relationship('Repository')
+
+    @property
+    def field_key_prefixed(self):
+        return 'ex_%s' % self.field_key
+
+    @classmethod
+    def un_prefix_key(cls, key):
+        if key.startswith(cls.PREFIX):
+            return key[len(cls.PREFIX):]
+        return key
+
+    @classmethod
+    def get_by_key_name(cls, key, repo):
+        row = cls.query()\
+                .filter(cls.repository == repo)\
+                .filter(cls.field_key == key).scalar()
+        return row
+
+
+class Repository(Base, BaseModel):
+    __tablename__ = 'repositories'
+    __table_args__ = (
+        UniqueConstraint('repo_name'),
+        Index('r_repo_name_idx', 'repo_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
+    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
+    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
+    description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
+
+    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
+
+    user = relationship('User')
+    fork = relationship('Repository', remote_side=repo_id)
+    group = relationship('RepoGroup')
+    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    stats = relationship('Statistics', cascade='all', uselist=False)
+
+    followers = relationship('UserFollowing',
+                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
+                             cascade='all')
+    extra_fields = relationship('RepositoryField',
+                                cascade="all, delete, delete-orphan")
+
+    logs = relationship('UserLog')
+    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
+
+    pull_requests_org = relationship('PullRequest',
+                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    pull_requests_other = relationship('PullRequest',
+                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
+                                   safe_unicode(self.repo_name))
+
+    @hybrid_property
+    def locked(self):
+        # always should return [user_id, timelocked]
+        if self._locked:
+            _lock_info = self._locked.split(':')
+            return int(_lock_info[0]), _lock_info[1]
+        return [None, None]
+
+    @locked.setter
+    def locked(self, val):
+        if val and isinstance(val, (list, tuple)):
+            self._locked = ':'.join(map(str, val))
+        else:
+            self._locked = None
+
+    @hybrid_property
+    def changeset_cache(self):
+        from rhodecode.lib.vcs.backends.base import EmptyChangeset
+        dummy = EmptyChangeset().__json__()
+        if not self._changeset_cache:
+            return dummy
+        try:
+            return json.loads(self._changeset_cache)
+        except TypeError:
+            return dummy
+
+    @changeset_cache.setter
+    def changeset_cache(self, val):
+        try:
+            self._changeset_cache = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def normalize_repo_name(cls, repo_name):
+        """
+        Normalizes os specific repo_name to the format internally stored inside
+        dabatabase using URL_SEP
+
+        :param cls:
+        :param repo_name:
+        """
+        return cls.url_sep().join(repo_name.split(os.sep))
+
+    @classmethod
+    def get_by_repo_name(cls, repo_name):
+        q = Session().query(cls).filter(cls.repo_name == repo_name)
+        q = q.options(joinedload(Repository.fork))\
+                .options(joinedload(Repository.user))\
+                .options(joinedload(Repository.group))
+        return q.scalar()
+
+    @classmethod
+    def get_by_full_path(cls, repo_full_path):
+        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
+        repo_name = cls.normalize_repo_name(repo_name)
+        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
+
+    @classmethod
+    def get_repo_forks(cls, repo_id):
+        return cls.query().filter(Repository.fork_id == repo_id)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all repos are stored
+
+        :param cls:
+        """
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == cls.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def forks(self):
+        """
+        Return forks of this repo
+        """
+        return Repository.get_repo_forks(self.repo_id)
+
+    @property
+    def parent(self):
+        """
+        Returns fork parent
+        """
+        return self.fork
+
+    @property
+    def just_name(self):
+        return self.repo_name.split(Repository.url_sep())[-1]
+
+    @property
+    def groups_with_parents(self):
+        groups = []
+        if self.group is None:
+            return groups
+
+        cur_gr = self.group
+        groups.insert(0, cur_gr)
+        while 1:
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            groups.insert(0, gr)
+
+        return groups
+
+    @property
+    def groups_and_repo(self):
+        return self.groups_with_parents, self.just_name, self.repo_name
+
+    @LazyProperty
+    def repo_path(self):
+        """
+        Returns base full path for that repository means where it actually
+        exists on a filesystem
+        """
+        q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+                                              Repository.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def repo_full_path(self):
+        p = [self.repo_path]
+        # we need to split the name by / since this is how we store the
+        # names in the database, but that eventually needs to be converted
+        # into a valid system path
+        p += self.repo_name.split(Repository.url_sep())
+        return os.path.join(*map(safe_unicode, p))
+
+    @property
+    def cache_keys(self):
+        """
+        Returns associated cache keys for that repo
+        """
+        return CacheInvalidation.query()\
+            .filter(CacheInvalidation.cache_args == self.repo_name)\
+            .order_by(CacheInvalidation.cache_key)\
+            .all()
+
+    def get_new_name(self, repo_name):
+        """
+        returns new full repository name based on assigned group and new new
+
+        :param group_name:
+        """
+        path_prefix = self.group.full_path_splitted if self.group else []
+        return Repository.url_sep().join(path_prefix + [repo_name])
+
+    @property
+    def _ui(self):
+        """
+        Creates an db based ui object for this repository
+        """
+        from rhodecode.lib.utils import make_ui
+        return make_ui('db', clear_session=False)
+
+    @classmethod
+    def is_valid(cls, repo_name):
+        """
+        returns True if given repo name is a valid filesystem repository
+
+        :param cls:
+        :param repo_name:
+        """
+        from rhodecode.lib.utils import is_valid_repo
+
+        return is_valid_repo(repo_name, cls.base_path())
+
+    def get_api_data(self):
+        """
+        Common function for generating repo api data
+
+        """
+        repo = self
+        data = dict(
+            repo_id=repo.repo_id,
+            repo_name=repo.repo_name,
+            repo_type=repo.repo_type,
+            clone_uri=repo.clone_uri,
+            private=repo.private,
+            created_on=repo.created_on,
+            description=repo.description,
+            landing_rev=repo.landing_rev,
+            owner=repo.user.username,
+            fork_of=repo.fork.repo_name if repo.fork else None,
+            enable_statistics=repo.enable_statistics,
+            enable_locking=repo.enable_locking,
+            enable_downloads=repo.enable_downloads,
+            last_changeset=repo.changeset_cache,
+            locked_by=User.get(self.locked[0]).get_api_data() \
+                if self.locked[0] else None,
+            locked_date=time_to_datetime(self.locked[1]) \
+                if self.locked[1] else None
+        )
+        rc_config = RhodeCodeSetting.get_app_settings()
+        repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
+        if repository_fields:
+            for f in self.extra_fields:
+                data[f.field_key_prefixed] = f.field_value
+
+        return data
+
+    @classmethod
+    def lock(cls, repo, user_id, lock_time=None):
+        if not lock_time:
+            lock_time = time.time()
+        repo.locked = [user_id, lock_time]
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def unlock(cls, repo):
+        repo.locked = None
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def getlock(cls, repo):
+        return repo.locked
+
+    @property
+    def last_db_change(self):
+        return self.updated_on
+
+    def clone_url(self, **override):
+        from pylons import url
+        from urlparse import urlparse
+        import urllib
+        parsed_url = urlparse(url('home', qualified=True))
+        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
+        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
+        args = {
+           'user': '',
+           'pass': '',
+           'scheme': parsed_url.scheme,
+           'netloc': parsed_url.netloc,
+           'prefix': decoded_path,
+           'path': self.repo_name
+        }
+
+        args.update(override)
+        return default_clone_uri % args
+
+    #==========================================================================
+    # SCM PROPERTIES
+    #==========================================================================
+
+    def get_changeset(self, rev=None):
+        return get_changeset_safe(self.scm_instance, rev)
+
+    def get_landing_changeset(self):
+        """
+        Returns landing changeset, or if that doesn't exist returns the tip
+        """
+        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
+        return cs
+
+    def update_changeset_cache(self, cs_cache=None):
+        """
+        Update cache of last changeset for repository, keys should be::
+
+            short_id
+            raw_id
+            revision
+            message
+            date
+            author
+
+        :param cs_cache:
+        """
+        from rhodecode.lib.vcs.backends.base import BaseChangeset
+        if cs_cache is None:
+            cs_cache = EmptyChangeset()
+            # use no-cache version here
+            scm_repo = self.scm_instance_no_cache()
+            if scm_repo:
+                cs_cache = scm_repo.get_changeset()
+
+        if isinstance(cs_cache, BaseChangeset):
+            cs_cache = cs_cache.__json__()
+
+        if (cs_cache != self.changeset_cache or not self.changeset_cache):
+            _default = datetime.datetime.fromtimestamp(0)
+            last_change = cs_cache.get('date') or _default
+            log.debug('updated repo %s with new cs cache %s'
+                      % (self.repo_name, cs_cache))
+            self.updated_on = last_change
+            self.changeset_cache = cs_cache
+            Session().add(self)
+            Session().commit()
+        else:
+            log.debug('Skipping repo:%s already with latest changes'
+                      % self.repo_name)
+
+    @property
+    def tip(self):
+        return self.get_changeset('tip')
+
+    @property
+    def author(self):
+        return self.tip.author
+
+    @property
+    def last_change(self):
+        return self.scm_instance.last_change
+
+    def get_comments(self, revisions=None):
+        """
+        Returns comments for this repository grouped by revisions
+
+        :param revisions: filter query by revisions only
+        """
+        cmts = ChangesetComment.query()\
+            .filter(ChangesetComment.repo == self)
+        if revisions:
+            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
+        grouped = collections.defaultdict(list)
+        for cmt in cmts.all():
+            grouped[cmt.revision].append(cmt)
+        return grouped
+
+    def statuses(self, revisions=None):
+        """
+        Returns statuses for this repository
+
+        :param revisions: list of revisions to get statuses for
+        """
+
+        statuses = ChangesetStatus.query()\
+            .filter(ChangesetStatus.repo == self)\
+            .filter(ChangesetStatus.version == 0)
+        if revisions:
+            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
+        grouped = {}
+
+        #maybe we have open new pullrequest without a status ?
+        stat = ChangesetStatus.STATUS_UNDER_REVIEW
+        status_lbl = ChangesetStatus.get_status_lbl(stat)
+        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
+            for rev in pr.revisions:
+                pr_id = pr.pull_request_id
+                pr_repo = pr.other_repo.repo_name
+                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
+
+        for stat in statuses.all():
+            pr_id = pr_repo = None
+            if stat.pull_request:
+                pr_id = stat.pull_request.pull_request_id
+                pr_repo = stat.pull_request.other_repo.repo_name
+            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
+                                      pr_id, pr_repo]
+        return grouped
+
+    def _repo_size(self):
+        from rhodecode.lib import helpers as h
+        log.debug('calculating repository size...')
+        return h.format_byte_size(self.scm_instance.size)
+
+    #==========================================================================
+    # SCM CACHE INSTANCE
+    #==========================================================================
+
+    def set_invalidate(self):
+        """
+        Mark caches of this repo as invalid.
+        """
+        CacheInvalidation.set_invalidate(self.repo_name)
+
+    def scm_instance_no_cache(self):
+        return self.__get_instance()
+
+    @property
+    def scm_instance(self):
+        import rhodecode
+        full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
+        if full_cache:
+            return self.scm_instance_cached()
+        return self.__get_instance()
+
+    def scm_instance_cached(self, valid_cache_keys=None):
+        @cache_region('long_term')
+        def _c(repo_name):
+            return self.__get_instance()
+        rn = self.repo_name
+
+        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
+        if not valid:
+            log.debug('Cache for %s invalidated, getting new object' % (rn))
+            region_invalidate(_c, None, rn)
+        else:
+            log.debug('Getting obj for %s from cache' % (rn))
+        return _c(rn)
+
+    def __get_instance(self):
+        repo_full_path = self.repo_full_path
+        try:
+            alias = get_scm(repo_full_path)[0]
+            log.debug('Creating instance of %s repository from %s'
+                      % (alias, repo_full_path))
+            backend = get_backend(alias)
+        except VCSError:
+            log.error(traceback.format_exc())
+            log.error('Perhaps this repository is in db and not in '
+                      'filesystem run rescan repositories with '
+                      '"destroy old data " option from admin panel')
+            return
+
+        if alias == 'hg':
+
+            repo = backend(safe_str(repo_full_path), create=False,
+                           baseui=self._ui)
+            # skip hidden web repository
+            if repo._get_hidden():
+                return
+        else:
+            repo = backend(repo_full_path, create=False)
+
+        return repo
+
+
+class RepoGroup(Base, BaseModel):
+    __tablename__ = 'groups'
+    __table_args__ = (
+        UniqueConstraint('group_name', 'group_parent_id'),
+        CheckConstraint('group_id != group_parent_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    __mapper_args__ = {'order_by': 'group_name'}
+
+    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
+    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    parent_group = relationship('RepoGroup', remote_side=group_id)
+    user = relationship('User')
+
+    def __init__(self, group_name='', parent_group=None):
+        self.group_name = group_name
+        self.parent_group = parent_group
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
+                                      self.group_name)
+
+    @classmethod
+    def groups_choices(cls, groups=None, show_empty_group=True):
+        from webhelpers.html import literal as _literal
+        if not groups:
+            groups = cls.query().all()
+
+        repo_groups = []
+        if show_empty_group:
+            repo_groups = [('-1', u'-- %s --' % _('top level'))]
+        sep = ' &raquo; '
+        _name = lambda k: _literal(sep.join(k))
+
+        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
+                              for x in groups])
+
+        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
+        return repo_groups
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+        if case_insensitive:
+            gr = cls.query()\
+                .filter(cls.group_name.ilike(group_name))
+        else:
+            gr = cls.query()\
+                .filter(cls.group_name == group_name)
+        if cache:
+            gr = gr.options(FromCache(
+                            "sql_cache_short",
+                            "get_group_%s" % _hash_key(group_name)
+                            )
+            )
+        return gr.scalar()
+
+    @property
+    def parents(self):
+        parents_recursion_limit = 5
+        groups = []
+        if self.parent_group is None:
+            return groups
+        cur_gr = self.parent_group
+        groups.insert(0, cur_gr)
+        cnt = 0
+        while 1:
+            cnt += 1
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            if cnt == parents_recursion_limit:
+                # this will prevent accidental infinit loops
+                log.error('group nested more than %s' %
+                          parents_recursion_limit)
+                break
+
+            groups.insert(0, gr)
+        return groups
+
+    @property
+    def children(self):
+        return RepoGroup.query().filter(RepoGroup.parent_group == self)
+
+    @property
+    def name(self):
+        return self.group_name.split(RepoGroup.url_sep())[-1]
+
+    @property
+    def full_path(self):
+        return self.group_name
+
+    @property
+    def full_path_splitted(self):
+        return self.group_name.split(RepoGroup.url_sep())
+
+    @property
+    def repositories(self):
+        return Repository.query()\
+                .filter(Repository.group == self)\
+                .order_by(Repository.repo_name)
+
+    @property
+    def repositories_recursive_count(self):
+        cnt = self.repositories.count()
+
+        def children_count(group):
+            cnt = 0
+            for child in group.children:
+                cnt += child.repositories.count()
+                cnt += children_count(child)
+            return cnt
+
+        return cnt + children_count(self)
+
+    def _recursive_objects(self, include_repos=True):
+        all_ = []
+
+        def _get_members(root_gr):
+            if include_repos:
+                for r in root_gr.repositories:
+                    all_.append(r)
+            childs = root_gr.children.all()
+            if childs:
+                for gr in childs:
+                    all_.append(gr)
+                    _get_members(gr)
+
+        _get_members(self)
+        return [self] + all_
+
+    def recursive_groups_and_repos(self):
+        """
+        Recursive return all groups, with repositories in those groups
+        """
+        return self._recursive_objects()
+
+    def recursive_groups(self):
+        """
+        Returns all children groups for this group including children of children
+        """
+        return self._recursive_objects(include_repos=False)
+
+    def get_new_name(self, group_name):
+        """
+        returns new full group name based on parent and new name
+
+        :param group_name:
+        """
+        path_prefix = (self.parent_group.full_path_splitted if
+                       self.parent_group else [])
+        return RepoGroup.url_sep().join(path_prefix + [group_name])
+
+    def get_api_data(self):
+        """
+        Common function for generating api data
+
+        """
+        group = self
+        data = dict(
+            group_id=group.group_id,
+            group_name=group.group_name,
+            group_description=group.group_description,
+            parent_group=group.parent_group.group_name if group.parent_group else None,
+            repositories=[x.repo_name for x in group.repositories],
+            owner=group.user.username
+        )
+        return data
+
+
+class Permission(Base, BaseModel):
+    __tablename__ = 'permissions'
+    __table_args__ = (
+        Index('p_perm_name_idx', 'permission_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PERMS = [
+        ('hg.admin', _('RhodeCode Administrator')),
+
+        ('repository.none', _('Repository no access')),
+        ('repository.read', _('Repository read access')),
+        ('repository.write', _('Repository write access')),
+        ('repository.admin', _('Repository admin access')),
+
+        ('group.none', _('Repository group no access')),
+        ('group.read', _('Repository group read access')),
+        ('group.write', _('Repository group write access')),
+        ('group.admin', _('Repository group admin access')),
+
+        ('usergroup.none', _('User group no access')),
+        ('usergroup.read', _('User group read access')),
+        ('usergroup.write', _('User group write access')),
+        ('usergroup.admin', _('User group admin access')),
+
+        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
+        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
+
+        ('hg.usergroup.create.false', _('User Group creation disabled')),
+        ('hg.usergroup.create.true', _('User Group creation enabled')),
+
+        ('hg.create.none', _('Repository creation disabled')),
+        ('hg.create.repository', _('Repository creation enabled')),
+
+        ('hg.fork.none', _('Repository forking disabled')),
+        ('hg.fork.repository', _('Repository forking enabled')),
+
+        ('hg.register.none', _('Registration disabled')),
+        ('hg.register.manual_activate', _('User Registration with manual account activation')),
+        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
+
+        ('hg.extern_activate.manual', _('Manual activation of external account')),
+        ('hg.extern_activate.auto', _('Automatic activation of external account')),
+
+    ]
+
+    #definition of system default permissions for DEFAULT user
+    DEFAULT_USER_PERMISSIONS = [
+        'repository.read',
+        'group.read',
+        'usergroup.read',
+        'hg.create.repository',
+        'hg.fork.repository',
+        'hg.register.manual_activate',
+        'hg.extern_activate.auto',
+    ]
+
+    # defines which permissions are more important higher the more important
+    # Weight defines which permissions are more important.
+    # The higher number the more important.
+    PERM_WEIGHTS = {
+        'repository.none': 0,
+        'repository.read': 1,
+        'repository.write': 3,
+        'repository.admin': 4,
+
+        'group.none': 0,
+        'group.read': 1,
+        'group.write': 3,
+        'group.admin': 4,
+
+        'usergroup.none': 0,
+        'usergroup.read': 1,
+        'usergroup.write': 3,
+        'usergroup.admin': 4,
+        'hg.repogroup.create.false': 0,
+        'hg.repogroup.create.true': 1,
+
+        'hg.usergroup.create.false': 0,
+        'hg.usergroup.create.true': 1,
+
+        'hg.fork.none': 0,
+        'hg.fork.repository': 1,
+        'hg.create.none': 0,
+        'hg.create.repository': 1
+    }
+
+    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__, self.permission_id, self.permission_name
+        )
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.permission_name == key).scalar()
+
+    @classmethod
+    def get_default_perms(cls, default_user_id):
+        q = Session().query(UserRepoToPerm, Repository, cls)\
+         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
+         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_group_perms(cls, default_user_id):
+        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
+         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
+         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_user_group_perms(cls, default_user_id):
+        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
+         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
+         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserUserGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+
+class UserRepoToPerm(Base, BaseModel):
+    __tablename__ = 'repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    repository = relationship('Repository')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, repository, permission):
+        n = cls()
+        n.user = user
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.repository)
+
+
+class UserUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    user_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, user_group, permission):
+        n = cls()
+        n.user = user
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.user_group)
+
+
+class UserToPerm(Base, BaseModel):
+    __tablename__ = 'user_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    permission = relationship('Permission', lazy='joined')
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.permission)
+
+
+class UserGroupRepoToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    repository = relationship('Repository')
+
+    @classmethod
+    def create(cls, users_group, repository, permission):
+        n = cls()
+        n.users_group = users_group
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
+
+
+class UserGroupUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_group_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
+        CheckConstraint('target_user_group_id != user_group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
+    user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, target_user_group, user_group, permission):
+        n = cls()
+        n.target_user_group = target_user_group
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
+
+
+class UserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'permission_id',),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+
+class UserRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    group = relationship('RepoGroup')
+    permission = relationship('Permission')
+
+
+class UserGroupRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    group = relationship('RepoGroup')
+
+
+class Statistics(Base, BaseModel):
+    __tablename__ = 'statistics'
+    __table_args__ = (
+         UniqueConstraint('repository_id'),
+         {'extend_existing': True, 'mysql_engine': 'InnoDB',
+          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
+    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
+    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
+    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
+
+    repository = relationship('Repository', single_parent=True)
+
+
+class UserFollowing(Base, BaseModel):
+    __tablename__ = 'user_followings'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'follows_repository_id'),
+        UniqueConstraint('user_id', 'follows_user_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
+    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+
+    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
+
+    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
+    follows_repository = relationship('Repository', order_by='Repository.repo_name')
+
+    @classmethod
+    def get_repo_followers(cls, repo_id):
+        return cls.query().filter(cls.follows_repo_id == repo_id)
+
+
+class CacheInvalidation(Base, BaseModel):
+    __tablename__ = 'cache_invalidation'
+    __table_args__ = (
+        UniqueConstraint('cache_key'),
+        Index('key_idx', 'cache_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    # cache_id, not used
+    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    # cache_key as created by _get_cache_key
+    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    # cache_args is a repo_name
+    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    # instance sets cache_active True when it is caching,
+    # other instances set cache_active to False to indicate that this cache is invalid
+    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+
+    def __init__(self, cache_key, repo_name=''):
+        self.cache_key = cache_key
+        self.cache_args = repo_name
+        self.cache_active = False
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
+                            self.cache_id, self.cache_key, self.cache_active)
+
+    def _cache_key_partition(self):
+        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
+        return prefix, repo_name, suffix
+
+    def get_prefix(self):
+        """
+        get prefix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[0]
+
+    def get_suffix(self):
+        """
+        get suffix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[2]
+
+    @classmethod
+    def clear_cache(cls):
+        """
+        Delete all cache keys from database.
+        Should only be run when all instances are down and all entries thus stale.
+        """
+        cls.query().delete()
+        Session().commit()
+
+    @classmethod
+    def _get_cache_key(cls, key):
+        """
+        Wrapper for generating a unique cache key for this instance and "key".
+        key must / will start with a repo_name which will be stored in .cache_args .
+        """
+        import rhodecode
+        prefix = rhodecode.CONFIG.get('instance_id', '')
+        return "%s%s" % (prefix, key)
+
+    @classmethod
+    def set_invalidate(cls, repo_name, delete=False):
+        """
+        Mark all caches of a repo as invalid in the database.
+        """
+        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
+
+        try:
+            for inv_obj in inv_objs:
+                log.debug('marking %s key for invalidation based on repo_name=%s'
+                          % (inv_obj, safe_str(repo_name)))
+                if delete:
+                    Session().delete(inv_obj)
+                else:
+                    inv_obj.cache_active = False
+                    Session().add(inv_obj)
+            Session().commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+
+    @classmethod
+    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
+        """
+        Mark this cache key as active and currently cached.
+        Return True if the existing cache registration still was valid.
+        Return False to indicate that it had been invalidated and caches should be refreshed.
+        """
+
+        key = (repo_name + '_' + kind) if kind else repo_name
+        cache_key = cls._get_cache_key(key)
+
+        if valid_cache_keys and cache_key in valid_cache_keys:
+            return True
+
+        try:
+            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
+            if not inv_obj:
+                inv_obj = CacheInvalidation(cache_key, repo_name)
+            was_valid = inv_obj.cache_active
+            inv_obj.cache_active = True
+            Session().add(inv_obj)
+            Session().commit()
+            return was_valid
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+            return False
+
+    @classmethod
+    def get_valid_cache_keys(cls):
+        """
+        Return opaque object with information of which caches still are valid
+        and can be used without checking for invalidation.
+        """
+        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
+
+
+class ChangesetComment(Base, BaseModel):
+    __tablename__ = 'changeset_comments'
+    __table_args__ = (
+        Index('cc_revision_idx', 'revision'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    revision = Column('revision', String(40), nullable=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+    line_no = Column('line_no', Unicode(10), nullable=True)
+    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
+    f_path = Column('f_path', Unicode(1000), nullable=True)
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+    text = Column('text', UnicodeText(25000), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    @classmethod
+    def get_users(cls, revision=None, pull_request_id=None):
+        """
+        Returns user associated with this ChangesetComment. ie those
+        who actually commented
+
+        :param cls:
+        :param revision:
+        """
+        q = Session().query(User)\
+                .join(ChangesetComment.author)
+        if revision:
+            q = q.filter(cls.revision == revision)
+        elif pull_request_id:
+            q = q.filter(cls.pull_request_id == pull_request_id)
+        return q.all()
+
+
+class ChangesetStatus(Base, BaseModel):
+    __tablename__ = 'changeset_statuses'
+    __table_args__ = (
+        Index('cs_revision_idx', 'revision'),
+        Index('cs_version_idx', 'version'),
+        UniqueConstraint('repo_id', 'revision', 'version'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
+    STATUS_APPROVED = 'approved'
+    STATUS_REJECTED = 'rejected'
+    STATUS_UNDER_REVIEW = 'under_review'
+
+    STATUSES = [
+        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
+        (STATUS_APPROVED, _("Approved")),
+        (STATUS_REJECTED, _("Rejected")),
+        (STATUS_UNDER_REVIEW, _("Under Review")),
+    ]
+
+    changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    revision = Column('revision', String(40), nullable=False)
+    status = Column('status', String(128), nullable=False, default=DEFAULT)
+    changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
+    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
+    version = Column('version', Integer(), nullable=False, default=0)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    comment = relationship('ChangesetComment', lazy='joined')
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__,
+            self.status, self.author
+        )
+
+    @classmethod
+    def get_status_lbl(cls, value):
+        return dict(cls.STATUSES).get(value)
+
+    @property
+    def status_lbl(self):
+        return ChangesetStatus.get_status_lbl(self.status)
+
+
+class PullRequest(Base, BaseModel):
+    __tablename__ = 'pull_requests'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    # values for .status
+    STATUS_NEW = u'new'
+    STATUS_OPEN = u'open'
+    STATUS_CLOSED = u'closed'
+
+    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
+    title = Column('title', Unicode(256), nullable=True)
+    description = Column('description', UnicodeText(10240), nullable=True)
+    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    _revisions = Column('revisions', UnicodeText(20500))  # 500 revisions max
+    org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    org_ref = Column('org_ref', Unicode(256), nullable=False)
+    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    other_ref = Column('other_ref', Unicode(256), nullable=False)
+
+    @hybrid_property
+    def revisions(self):
+        return self._revisions.split(':')
+
+    @revisions.setter
+    def revisions(self, val):
+        self._revisions = ':'.join(val)
+
+    @property
+    def org_ref_parts(self):
+        return self.org_ref.split(':')
+
+    @property
+    def other_ref_parts(self):
+        return self.other_ref.split(':')
+
+    author = relationship('User', lazy='joined')
+    reviewers = relationship('PullRequestReviewers',
+                             cascade="all, delete, delete-orphan")
+    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
+    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
+    statuses = relationship('ChangesetStatus')
+    comments = relationship('ChangesetComment',
+                             cascade="all, delete, delete-orphan")
+
+    def is_closed(self):
+        return self.status == self.STATUS_CLOSED
+
+    @property
+    def last_review_status(self):
+        return self.statuses[-1].status if self.statuses else ''
+
+    def __json__(self):
+        return dict(
+            revisions=self.revisions
+        )
+
+
+class PullRequestReviewers(Base, BaseModel):
+    __tablename__ = 'pull_request_reviewers'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    def __init__(self, user=None, pull_request=None):
+        self.user = user
+        self.pull_request = pull_request
+
+    pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
+
+    user = relationship('User')
+    pull_request = relationship('PullRequest')
+
+
+class Notification(Base, BaseModel):
+    __tablename__ = 'notifications'
+    __table_args__ = (
+        Index('notification_type_idx', 'type'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    TYPE_CHANGESET_COMMENT = u'cs_comment'
+    TYPE_MESSAGE = u'message'
+    TYPE_MENTION = u'mention'
+    TYPE_REGISTRATION = u'registration'
+    TYPE_PULL_REQUEST = u'pull_request'
+    TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
+
+    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
+    subject = Column('subject', Unicode(512), nullable=True)
+    body = Column('body', UnicodeText(50000), nullable=True)
+    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    type_ = Column('type', Unicode(256))
+
+    created_by_user = relationship('User')
+    notifications_to_users = relationship('UserNotification', lazy='joined',
+                                          cascade="all, delete, delete-orphan")
+
+    @property
+    def recipients(self):
+        return [x.user for x in UserNotification.query()\
+                .filter(UserNotification.notification == self)\
+                .order_by(UserNotification.user_id.asc()).all()]
+
+    @classmethod
+    def create(cls, created_by, subject, body, recipients, type_=None):
+        if type_ is None:
+            type_ = Notification.TYPE_MESSAGE
+
+        notification = cls()
+        notification.created_by_user = created_by
+        notification.subject = subject
+        notification.body = body
+        notification.type_ = type_
+        notification.created_on = datetime.datetime.now()
+
+        for u in recipients:
+            assoc = UserNotification()
+            assoc.notification = notification
+            u.notifications.append(assoc)
+        Session().add(notification)
+        return notification
+
+    @property
+    def description(self):
+        from rhodecode.model.notification import NotificationModel
+        return NotificationModel().make_description(self)
+
+
+class UserNotification(Base, BaseModel):
+    __tablename__ = 'user_to_notification'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'notification_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
+    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
+    read = Column('read', Boolean, default=False)
+    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
+
+    user = relationship('User', lazy="joined")
+    notification = relationship('Notification', lazy="joined",
+                                order_by=lambda: Notification.created_on.desc(),)
+
+    def mark_as_read(self):
+        self.read = True
+        Session().add(self)
+
+
+class Gist(Base, BaseModel):
+    __tablename__ = 'gists'
+    __table_args__ = (
+        Index('g_gist_access_id_idx', 'gist_access_id'),
+        Index('g_created_on_idx', 'created_on'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    GIST_PUBLIC = u'public'
+    GIST_PRIVATE = u'private'
+
+    gist_id = Column('gist_id', Integer(), primary_key=True)
+    gist_access_id = Column('gist_access_id', Unicode(250))
+    gist_description = Column('gist_description', UnicodeText(1024))
+    gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
+    gist_expires = Column('gist_expires', Float(53), nullable=False)
+    gist_type = Column('gist_type', Unicode(128), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    owner = relationship('User')
+
+    @classmethod
+    def get_or_404(cls, id_):
+        res = cls.query().filter(cls.gist_access_id == id_).scalar()
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def get_by_access_id(cls, gist_access_id):
+        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
+
+    def gist_url(self):
+        import rhodecode
+        alias_url = rhodecode.CONFIG.get('gist_alias_url')
+        if alias_url:
+            return alias_url.replace('{gistid}', self.gist_access_id)
+
+        from pylons import url
+        return url('gist', gist_id=self.gist_access_id, qualified=True)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all gists are stored
+
+        :param cls:
+        """
+        from rhodecode.model.gist import GIST_STORE_LOC
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == URL_SEP)
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
+
+    def get_api_data(self):
+        """
+        Common function for generating gist related data for API
+        """
+        gist = self
+        data = dict(
+            gist_id=gist.gist_id,
+            type=gist.gist_type,
+            access_id=gist.gist_access_id,
+            description=gist.gist_description,
+            url=gist.gist_url(),
+            expires=gist.gist_expires,
+            created_on=gist.created_on,
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+        )
+        data.update(self.get_api_data())
+        return data
+    ## SCM functions
+
+    @property
+    def scm_instance(self):
+        from rhodecode.lib.vcs import get_repo
+        base_path = self.base_path()
+        return get_repo(os.path.join(*map(safe_str,
+                                          [base_path, self.gist_access_id])))
+
+
+class DbMigrateVersion(Base, BaseModel):
+    __tablename__ = 'db_migrate_version'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    repository_id = Column('repository_id', String(250), primary_key=True)
+    repository_path = Column('repository_path', Text)
+    version = Column('version', Integer)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/schema/db_2_0_1.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,2331 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db
+~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import os
+import time
+import logging
+import datetime
+import traceback
+import hashlib
+import collections
+import functools
+
+from sqlalchemy import *
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
+from sqlalchemy.exc import DatabaseError
+from beaker.cache import cache_region, region_invalidate
+from webob.exc import HTTPNotFound
+
+from pylons.i18n.translation import lazy_ugettext as _
+
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
+
+from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
+    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int
+from rhodecode.lib.compat import json
+from rhodecode.lib.caching_query import FromCache
+
+from rhodecode.model.meta import Base, Session
+
+URL_SEP = '/'
+log = logging.getLogger(__name__)
+
+#==============================================================================
+# BASE CLASSES
+#==============================================================================
+
+_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
+
+
+class BaseModel(object):
+    """
+    Base Model for all classess
+    """
+
+    @classmethod
+    def _get_keys(cls):
+        """return column names for this model """
+        return class_mapper(cls).c.keys()
+
+    def get_dict(self):
+        """
+        return dict with keys and values corresponding
+        to this model data """
+
+        d = {}
+        for k in self._get_keys():
+            d[k] = getattr(self, k)
+
+        # also use __json__() if present to get additional fields
+        _json_attr = getattr(self, '__json__', None)
+        if _json_attr:
+            # update with attributes from __json__
+            if callable(_json_attr):
+                _json_attr = _json_attr()
+            for k, val in _json_attr.iteritems():
+                d[k] = val
+        return d
+
+    def get_appstruct(self):
+        """return list with keys and values tupples corresponding
+        to this model data """
+
+        l = []
+        for k in self._get_keys():
+            l.append((k, getattr(self, k),))
+        return l
+
+    def populate_obj(self, populate_dict):
+        """populate model with data from given populate_dict"""
+
+        for k in self._get_keys():
+            if k in populate_dict:
+                setattr(self, k, populate_dict[k])
+
+    @classmethod
+    def query(cls):
+        return Session().query(cls)
+
+    @classmethod
+    def get(cls, id_):
+        if id_:
+            return cls.query().get(id_)
+
+    @classmethod
+    def get_or_404(cls, id_):
+        try:
+            id_ = int(id_)
+        except (TypeError, ValueError):
+            raise HTTPNotFound
+
+        res = cls.query().get(id_)
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def getAll(cls):
+        # deprecated and left for backward compatibility
+        return cls.get_all()
+
+    @classmethod
+    def get_all(cls):
+        return cls.query().all()
+
+    @classmethod
+    def delete(cls, id_):
+        obj = cls.query().get(id_)
+        Session().delete(obj)
+
+    def __repr__(self):
+        if hasattr(self, '__unicode__'):
+            # python repr needs to return str
+            try:
+                return safe_str(self.__unicode__())
+            except UnicodeDecodeError:
+                pass
+        return '<DB:%s>' % (self.__class__.__name__)
+
+
+class RhodeCodeSetting(Base, BaseModel):
+    SETTINGS_TYPES = {
+        'str': safe_str,
+        'int': safe_int,
+        'unicode': safe_unicode,
+        'bool': str2bool,
+        'list': functools.partial(aslist, sep=',')
+    }
+    __tablename__ = 'rhodecode_settings'
+    __table_args__ = (
+        UniqueConstraint('app_settings_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __init__(self, key='', val='', type='unicode'):
+        self.app_settings_name = key
+        self.app_settings_value = val
+        self.app_settings_type = type
+
+    @validates('_app_settings_value')
+    def validate_settings_value(self, key, val):
+        assert type(val) == unicode
+        return val
+
+    @hybrid_property
+    def app_settings_value(self):
+        v = self._app_settings_value
+        _type = self.app_settings_type
+        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
+        return converter(v)
+
+    @app_settings_value.setter
+    def app_settings_value(self, val):
+        """
+        Setter that will always make sure we use unicode in app_settings_value
+
+        :param val:
+        """
+        self._app_settings_value = safe_unicode(val)
+
+    @hybrid_property
+    def app_settings_type(self):
+        return self._app_settings_type
+
+    @app_settings_type.setter
+    def app_settings_type(self, val):
+        if val not in self.SETTINGS_TYPES:
+            raise Exception('type must be one of %s got %s'
+                            % (self.SETTINGS_TYPES.keys(), val))
+        self._app_settings_type = val
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (
+            self.__class__.__name__,
+            self.app_settings_name, self.app_settings_value, self.app_settings_type
+        )
+
+    @classmethod
+    def get_by_name(cls, key):
+        return cls.query()\
+            .filter(cls.app_settings_name == key).scalar()
+
+    @classmethod
+    def get_by_name_or_create(cls, key, val='', type='unicode'):
+        res = cls.get_by_name(key)
+        if not res:
+            res = cls(key, val, type)
+        return res
+
+    @classmethod
+    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
+        """
+        Creates or updates RhodeCode setting. If updates is triggered it will only
+        update parameters that are explicityl set Optional instance will be skipped
+
+        :param key:
+        :param val:
+        :param type:
+        :return:
+        """
+        res = cls.get_by_name(key)
+        if not res:
+            val = Optional.extract(val)
+            type = Optional.extract(type)
+            res = cls(key, val, type)
+        else:
+            res.app_settings_name = key
+            if not isinstance(val, Optional):
+                # update if set
+                res.app_settings_value = val
+            if not isinstance(type, Optional):
+                # update if set
+                res.app_settings_type = type
+        return res
+
+    @classmethod
+    def get_app_settings(cls, cache=False):
+
+        ret = cls.query()
+
+        if cache:
+            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
+
+        if not ret:
+            raise Exception('Could not get application settings !')
+        settings = {}
+        for each in ret:
+            settings['rhodecode_' + each.app_settings_name] = \
+                each.app_settings_value
+
+        return settings
+
+    @classmethod
+    def get_auth_plugins(cls, cache=False):
+        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
+        return auth_plugins
+
+    @classmethod
+    def get_auth_settings(cls, cache=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('auth_')).all()
+        fd = {}
+        for row in ret:
+            fd.update({row.app_settings_name: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('default_')).all()
+        fd = {}
+        for row in ret:
+            key = row.app_settings_name
+            if strip_prefix:
+                key = remove_prefix(key, prefix='default_')
+            fd.update({key: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_server_info(cls):
+        import pkg_resources
+        import platform
+        import rhodecode
+        from rhodecode.lib.utils import check_git_version
+        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
+        info = {
+            'modules': sorted(mods, key=lambda k: k[0].lower()),
+            'py_version': platform.python_version(),
+            'platform': safe_unicode(platform.platform()),
+            'rhodecode_version': rhodecode.__version__,
+            'git_version': safe_unicode(check_git_version()),
+            'git_path': rhodecode.CONFIG.get('git_path')
+        }
+        return info
+
+
+class RhodeCodeUi(Base, BaseModel):
+    __tablename__ = 'rhodecode_ui'
+    __table_args__ = (
+        UniqueConstraint('ui_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    HOOK_UPDATE = 'changegroup.update'
+    HOOK_REPO_SIZE = 'changegroup.repo_size'
+    HOOK_PUSH = 'changegroup.push_logger'
+    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
+    HOOK_PULL = 'outgoing.pull_logger'
+    HOOK_PRE_PULL = 'preoutgoing.pre_pull'
+
+    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
+
+    # def __init__(self, section='', key='', value=''):
+    #     self.ui_section = section
+    #     self.ui_key = key
+    #     self.ui_value = value
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.ui_key == key).scalar()
+
+    @classmethod
+    def get_builtin_hooks(cls):
+        q = cls.query()
+        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        return q.all()
+
+    @classmethod
+    def get_custom_hooks(cls):
+        q = cls.query()
+        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        q = q.filter(cls.ui_section == 'hooks')
+        return q.all()
+
+    @classmethod
+    def get_repos_location(cls):
+        return cls.get_by_key('/').ui_value
+
+    @classmethod
+    def create_or_update_hook(cls, key, val):
+        new_ui = cls.get_by_key(key) or cls()
+        new_ui.ui_section = 'hooks'
+        new_ui.ui_active = True
+        new_ui.ui_key = key
+        new_ui.ui_value = val
+
+        Session().add(new_ui)
+
+    def __repr__(self):
+        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
+                                   self.ui_value)
+
+
+class User(Base, BaseModel):
+    __tablename__ = 'users'
+    __table_args__ = (
+        UniqueConstraint('username'), UniqueConstraint('email'),
+        Index('u_username_idx', 'username'),
+        Index('u_email_idx', 'email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    DEFAULT_USER = 'default'
+
+    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
+    extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    user_log = relationship('UserLog')
+    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
+
+    repositories = relationship('Repository')
+    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
+    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
+
+    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
+
+    group_member = relationship('UserGroupMember', cascade='all')
+
+    notifications = relationship('UserNotification', cascade='all')
+    # notifications assigned to this user
+    user_created_notifications = relationship('Notification', cascade='all')
+    # comments created by this user
+    user_comments = relationship('ChangesetComment', cascade='all')
+    #extra emails for this user
+    user_emails = relationship('UserEmailMap', cascade='all')
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+    @property
+    def firstname(self):
+        # alias for future
+        return self.name
+
+    @property
+    def emails(self):
+        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
+        return [self.email] + [x.email for x in other]
+
+    @property
+    def ip_addresses(self):
+        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
+        return [x.ip_addr for x in ret]
+
+    @property
+    def username_and_name(self):
+        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
+
+    @property
+    def full_name(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def full_name_or_username(self):
+        return ('%s %s' % (self.firstname, self.lastname)
+                if (self.firstname and self.lastname) else self.username)
+
+    @property
+    def full_contact(self):
+        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
+
+    @property
+    def short_contact(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def is_admin(self):
+        return self.admin
+
+    @property
+    def AuthUser(self):
+        """
+        Returns instance of AuthUser for this user
+        """
+        from rhodecode.lib.auth import AuthUser
+        return AuthUser(user_id=self.user_id, api_key=self.api_key,
+                        username=self.username)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.user_id, self.username)
+
+    @classmethod
+    def get_by_username(cls, username, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.username.ilike(username))
+        else:
+            q = cls.query().filter(cls.username == username)
+
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(username)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get_by_api_key(cls, api_key, cache=False):
+        q = cls.query().filter(cls.api_key == api_key)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_api_key_%s" % api_key))
+        return q.scalar()
+
+    @classmethod
+    def get_by_email(cls, email, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.email.ilike(email))
+        else:
+            q = cls.query().filter(cls.email == email)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_email_key_%s" % email))
+
+        ret = q.scalar()
+        if ret is None:
+            q = UserEmailMap.query()
+            # try fetching in alternate email map
+            if case_insensitive:
+                q = q.filter(UserEmailMap.email.ilike(email))
+            else:
+                q = q.filter(UserEmailMap.email == email)
+            q = q.options(joinedload(UserEmailMap.user))
+            if cache:
+                q = q.options(FromCache("sql_cache_short",
+                                        "get_email_map_key_%s" % email))
+            ret = getattr(q.scalar(), 'user', None)
+
+        return ret
+
+    @classmethod
+    def get_from_cs_author(cls, author):
+        """
+        Tries to get User objects out of commit author string
+
+        :param author:
+        """
+        from rhodecode.lib.helpers import email, author_name
+        # Valid email in the attribute passed, see if they're in the system
+        _email = email(author)
+        if _email:
+            user = cls.get_by_email(_email, case_insensitive=True)
+            if user:
+                return user
+        # Maybe we can match by username?
+        _author = author_name(author)
+        user = cls.get_by_username(_author, case_insensitive=True)
+        if user:
+            return user
+
+    def update_lastlogin(self):
+        """Update user lastlogin"""
+        self.last_login = datetime.datetime.now()
+        Session().add(self)
+        log.debug('updated user %s lastlogin' % self.username)
+
+    @classmethod
+    def get_first_admin(cls):
+        user = User.query().filter(User.admin == True).first()
+        if user is None:
+            raise Exception('Missing administrative account!')
+        return user
+
+    @classmethod
+    def get_default_user(cls, cache=False):
+        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
+        if user is None:
+            raise Exception('Missing default account!')
+        return user
+
+    def get_api_data(self):
+        """
+        Common function for generating user related data for API
+        """
+        user = self
+        data = dict(
+            user_id=user.user_id,
+            username=user.username,
+            firstname=user.name,
+            lastname=user.lastname,
+            email=user.email,
+            emails=user.emails,
+            api_key=user.api_key,
+            active=user.active,
+            admin=user.admin,
+            extern_type=user.extern_type,
+            extern_name=user.extern_name,
+            last_login=user.last_login,
+            ip_addresses=user.ip_addresses
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+            full_name=self.full_name,
+            full_name_or_username=self.full_name_or_username,
+            short_contact=self.short_contact,
+            full_contact=self.full_contact
+        )
+        data.update(self.get_api_data())
+        return data
+
+
+class UserEmailMap(Base, BaseModel):
+    __tablename__ = 'user_email_map'
+    __table_args__ = (
+        Index('uem_email_idx', 'email'),
+        UniqueConstraint('email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    user = relationship('User', lazy='joined')
+
+    @validates('_email')
+    def validate_email(self, key, email):
+        # check if this email is not main one
+        main_email = Session().query(User).filter(User.email == email).scalar()
+        if main_email is not None:
+            raise AttributeError('email %s is present is user table' % email)
+        return email
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+
+class UserIpMap(Base, BaseModel):
+    __tablename__ = 'user_ip_map'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'ip_addr'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    user = relationship('User', lazy='joined')
+
+    @classmethod
+    def _get_ip_range(cls, ip_addr):
+        from rhodecode.lib import ipaddr
+        net = ipaddr.IPNetwork(address=ip_addr)
+        return [str(net.network), str(net.broadcast)]
+
+    def __json__(self):
+        return dict(
+          ip_addr=self.ip_addr,
+          ip_range=self._get_ip_range(self.ip_addr)
+        )
+
+
+class UserLog(Base, BaseModel):
+    __tablename__ = 'user_logs'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.repository_name,
+                                      self.action)
+
+    @property
+    def action_as_day(self):
+        return datetime.date(*self.action_date.timetuple()[:3])
+
+    user = relationship('User')
+    repository = relationship('Repository', cascade='')
+
+
+class UserGroup(Base, BaseModel):
+    __tablename__ = 'users_groups'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
+    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
+    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
+    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
+
+    user = relationship('User')
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.users_group_id,
+                                      self.users_group_name)
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False,
+                          case_insensitive=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.users_group_name.ilike(group_name))
+        else:
+            q = cls.query().filter(cls.users_group_name == group_name)
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(group_name)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get(cls, user_group_id, cache=False):
+        user_group = cls.query()
+        if cache:
+            user_group = user_group.options(FromCache("sql_cache_short",
+                                    "get_users_group_%s" % user_group_id))
+        return user_group.get(user_group_id)
+
+    def get_api_data(self, with_members=True):
+        user_group = self
+
+        data = dict(
+            users_group_id=user_group.users_group_id,
+            group_name=user_group.users_group_name,
+            group_description=user_group.user_group_description,
+            active=user_group.users_group_active,
+            owner=user_group.user.username,
+        )
+        if with_members:
+            members = []
+            for user in user_group.members:
+                user = user.user
+                members.append(user.get_api_data())
+            data['members'] = members
+
+        return data
+
+
+class UserGroupMember(Base, BaseModel):
+    __tablename__ = 'users_groups_members'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User', lazy='joined')
+    users_group = relationship('UserGroup')
+
+    def __init__(self, gr_id='', u_id=''):
+        self.users_group_id = gr_id
+        self.user_id = u_id
+
+
+class RepositoryField(Base, BaseModel):
+    __tablename__ = 'repositories_fields'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PREFIX = 'ex_'  # prefix used in form to not conflict with already existing fields
+
+    repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
+    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_type = Column("field_type", String(256), nullable=False, unique=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repository = relationship('Repository')
+
+    @property
+    def field_key_prefixed(self):
+        return 'ex_%s' % self.field_key
+
+    @classmethod
+    def un_prefix_key(cls, key):
+        if key.startswith(cls.PREFIX):
+            return key[len(cls.PREFIX):]
+        return key
+
+    @classmethod
+    def get_by_key_name(cls, key, repo):
+        row = cls.query()\
+                .filter(cls.repository == repo)\
+                .filter(cls.field_key == key).scalar()
+        return row
+
+
+class Repository(Base, BaseModel):
+    __tablename__ = 'repositories'
+    __table_args__ = (
+        UniqueConstraint('repo_name'),
+        Index('r_repo_name_idx', 'repo_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
+    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
+    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
+    description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
+
+    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
+
+    user = relationship('User')
+    fork = relationship('Repository', remote_side=repo_id)
+    group = relationship('RepoGroup')
+    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    stats = relationship('Statistics', cascade='all', uselist=False)
+
+    followers = relationship('UserFollowing',
+                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
+                             cascade='all')
+    extra_fields = relationship('RepositoryField',
+                                cascade="all, delete, delete-orphan")
+
+    logs = relationship('UserLog')
+    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
+
+    pull_requests_org = relationship('PullRequest',
+                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    pull_requests_other = relationship('PullRequest',
+                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
+                                   safe_unicode(self.repo_name))
+
+    @hybrid_property
+    def locked(self):
+        # always should return [user_id, timelocked]
+        if self._locked:
+            _lock_info = self._locked.split(':')
+            return int(_lock_info[0]), _lock_info[1]
+        return [None, None]
+
+    @locked.setter
+    def locked(self, val):
+        if val and isinstance(val, (list, tuple)):
+            self._locked = ':'.join(map(str, val))
+        else:
+            self._locked = None
+
+    @hybrid_property
+    def changeset_cache(self):
+        from rhodecode.lib.vcs.backends.base import EmptyChangeset
+        dummy = EmptyChangeset().__json__()
+        if not self._changeset_cache:
+            return dummy
+        try:
+            return json.loads(self._changeset_cache)
+        except TypeError:
+            return dummy
+
+    @changeset_cache.setter
+    def changeset_cache(self, val):
+        try:
+            self._changeset_cache = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def normalize_repo_name(cls, repo_name):
+        """
+        Normalizes os specific repo_name to the format internally stored inside
+        dabatabase using URL_SEP
+
+        :param cls:
+        :param repo_name:
+        """
+        return cls.url_sep().join(repo_name.split(os.sep))
+
+    @classmethod
+    def get_by_repo_name(cls, repo_name):
+        q = Session().query(cls).filter(cls.repo_name == repo_name)
+        q = q.options(joinedload(Repository.fork))\
+                .options(joinedload(Repository.user))\
+                .options(joinedload(Repository.group))
+        return q.scalar()
+
+    @classmethod
+    def get_by_full_path(cls, repo_full_path):
+        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
+        repo_name = cls.normalize_repo_name(repo_name)
+        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
+
+    @classmethod
+    def get_repo_forks(cls, repo_id):
+        return cls.query().filter(Repository.fork_id == repo_id)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all repos are stored
+
+        :param cls:
+        """
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == cls.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def forks(self):
+        """
+        Return forks of this repo
+        """
+        return Repository.get_repo_forks(self.repo_id)
+
+    @property
+    def parent(self):
+        """
+        Returns fork parent
+        """
+        return self.fork
+
+    @property
+    def just_name(self):
+        return self.repo_name.split(Repository.url_sep())[-1]
+
+    @property
+    def groups_with_parents(self):
+        groups = []
+        if self.group is None:
+            return groups
+
+        cur_gr = self.group
+        groups.insert(0, cur_gr)
+        while 1:
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            groups.insert(0, gr)
+
+        return groups
+
+    @property
+    def groups_and_repo(self):
+        return self.groups_with_parents, self.just_name, self.repo_name
+
+    @LazyProperty
+    def repo_path(self):
+        """
+        Returns base full path for that repository means where it actually
+        exists on a filesystem
+        """
+        q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+                                              Repository.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def repo_full_path(self):
+        p = [self.repo_path]
+        # we need to split the name by / since this is how we store the
+        # names in the database, but that eventually needs to be converted
+        # into a valid system path
+        p += self.repo_name.split(Repository.url_sep())
+        return os.path.join(*map(safe_unicode, p))
+
+    @property
+    def cache_keys(self):
+        """
+        Returns associated cache keys for that repo
+        """
+        return CacheInvalidation.query()\
+            .filter(CacheInvalidation.cache_args == self.repo_name)\
+            .order_by(CacheInvalidation.cache_key)\
+            .all()
+
+    def get_new_name(self, repo_name):
+        """
+        returns new full repository name based on assigned group and new new
+
+        :param group_name:
+        """
+        path_prefix = self.group.full_path_splitted if self.group else []
+        return Repository.url_sep().join(path_prefix + [repo_name])
+
+    @property
+    def _ui(self):
+        """
+        Creates an db based ui object for this repository
+        """
+        from rhodecode.lib.utils import make_ui
+        return make_ui('db', clear_session=False)
+
+    @classmethod
+    def is_valid(cls, repo_name):
+        """
+        returns True if given repo name is a valid filesystem repository
+
+        :param cls:
+        :param repo_name:
+        """
+        from rhodecode.lib.utils import is_valid_repo
+
+        return is_valid_repo(repo_name, cls.base_path())
+
+    def get_api_data(self):
+        """
+        Common function for generating repo api data
+
+        """
+        repo = self
+        data = dict(
+            repo_id=repo.repo_id,
+            repo_name=repo.repo_name,
+            repo_type=repo.repo_type,
+            clone_uri=repo.clone_uri,
+            private=repo.private,
+            created_on=repo.created_on,
+            description=repo.description,
+            landing_rev=repo.landing_rev,
+            owner=repo.user.username,
+            fork_of=repo.fork.repo_name if repo.fork else None,
+            enable_statistics=repo.enable_statistics,
+            enable_locking=repo.enable_locking,
+            enable_downloads=repo.enable_downloads,
+            last_changeset=repo.changeset_cache,
+            locked_by=User.get(self.locked[0]).get_api_data() \
+                if self.locked[0] else None,
+            locked_date=time_to_datetime(self.locked[1]) \
+                if self.locked[1] else None
+        )
+        rc_config = RhodeCodeSetting.get_app_settings()
+        repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
+        if repository_fields:
+            for f in self.extra_fields:
+                data[f.field_key_prefixed] = f.field_value
+
+        return data
+
+    @classmethod
+    def lock(cls, repo, user_id, lock_time=None):
+        if not lock_time:
+            lock_time = time.time()
+        repo.locked = [user_id, lock_time]
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def unlock(cls, repo):
+        repo.locked = None
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def getlock(cls, repo):
+        return repo.locked
+
+    @property
+    def last_db_change(self):
+        return self.updated_on
+
+    def clone_url(self, **override):
+        from pylons import url
+        from urlparse import urlparse
+        import urllib
+        parsed_url = urlparse(url('home', qualified=True))
+        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
+        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
+        args = {
+           'user': '',
+           'pass': '',
+           'scheme': parsed_url.scheme,
+           'netloc': parsed_url.netloc,
+           'prefix': decoded_path,
+           'path': self.repo_name
+        }
+
+        args.update(override)
+        return default_clone_uri % args
+
+    #==========================================================================
+    # SCM PROPERTIES
+    #==========================================================================
+
+    def get_changeset(self, rev=None):
+        return get_changeset_safe(self.scm_instance, rev)
+
+    def get_landing_changeset(self):
+        """
+        Returns landing changeset, or if that doesn't exist returns the tip
+        """
+        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
+        return cs
+
+    def update_changeset_cache(self, cs_cache=None):
+        """
+        Update cache of last changeset for repository, keys should be::
+
+            short_id
+            raw_id
+            revision
+            message
+            date
+            author
+
+        :param cs_cache:
+        """
+        from rhodecode.lib.vcs.backends.base import BaseChangeset
+        if cs_cache is None:
+            cs_cache = EmptyChangeset()
+            # use no-cache version here
+            scm_repo = self.scm_instance_no_cache()
+            if scm_repo:
+                cs_cache = scm_repo.get_changeset()
+
+        if isinstance(cs_cache, BaseChangeset):
+            cs_cache = cs_cache.__json__()
+
+        if (cs_cache != self.changeset_cache or not self.changeset_cache):
+            _default = datetime.datetime.fromtimestamp(0)
+            last_change = cs_cache.get('date') or _default
+            log.debug('updated repo %s with new cs cache %s'
+                      % (self.repo_name, cs_cache))
+            self.updated_on = last_change
+            self.changeset_cache = cs_cache
+            Session().add(self)
+            Session().commit()
+        else:
+            log.debug('Skipping repo:%s already with latest changes'
+                      % self.repo_name)
+
+    @property
+    def tip(self):
+        return self.get_changeset('tip')
+
+    @property
+    def author(self):
+        return self.tip.author
+
+    @property
+    def last_change(self):
+        return self.scm_instance.last_change
+
+    def get_comments(self, revisions=None):
+        """
+        Returns comments for this repository grouped by revisions
+
+        :param revisions: filter query by revisions only
+        """
+        cmts = ChangesetComment.query()\
+            .filter(ChangesetComment.repo == self)
+        if revisions:
+            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
+        grouped = collections.defaultdict(list)
+        for cmt in cmts.all():
+            grouped[cmt.revision].append(cmt)
+        return grouped
+
+    def statuses(self, revisions=None):
+        """
+        Returns statuses for this repository
+
+        :param revisions: list of revisions to get statuses for
+        """
+
+        statuses = ChangesetStatus.query()\
+            .filter(ChangesetStatus.repo == self)\
+            .filter(ChangesetStatus.version == 0)
+        if revisions:
+            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
+        grouped = {}
+
+        #maybe we have open new pullrequest without a status ?
+        stat = ChangesetStatus.STATUS_UNDER_REVIEW
+        status_lbl = ChangesetStatus.get_status_lbl(stat)
+        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
+            for rev in pr.revisions:
+                pr_id = pr.pull_request_id
+                pr_repo = pr.other_repo.repo_name
+                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
+
+        for stat in statuses.all():
+            pr_id = pr_repo = None
+            if stat.pull_request:
+                pr_id = stat.pull_request.pull_request_id
+                pr_repo = stat.pull_request.other_repo.repo_name
+            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
+                                      pr_id, pr_repo]
+        return grouped
+
+    def _repo_size(self):
+        from rhodecode.lib import helpers as h
+        log.debug('calculating repository size...')
+        return h.format_byte_size(self.scm_instance.size)
+
+    #==========================================================================
+    # SCM CACHE INSTANCE
+    #==========================================================================
+
+    def set_invalidate(self):
+        """
+        Mark caches of this repo as invalid.
+        """
+        CacheInvalidation.set_invalidate(self.repo_name)
+
+    def scm_instance_no_cache(self):
+        return self.__get_instance()
+
+    @property
+    def scm_instance(self):
+        import rhodecode
+        full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
+        if full_cache:
+            return self.scm_instance_cached()
+        return self.__get_instance()
+
+    def scm_instance_cached(self, valid_cache_keys=None):
+        @cache_region('long_term')
+        def _c(repo_name):
+            return self.__get_instance()
+        rn = self.repo_name
+
+        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
+        if not valid:
+            log.debug('Cache for %s invalidated, getting new object' % (rn))
+            region_invalidate(_c, None, rn)
+        else:
+            log.debug('Getting obj for %s from cache' % (rn))
+        return _c(rn)
+
+    def __get_instance(self):
+        repo_full_path = self.repo_full_path
+        try:
+            alias = get_scm(repo_full_path)[0]
+            log.debug('Creating instance of %s repository from %s'
+                      % (alias, repo_full_path))
+            backend = get_backend(alias)
+        except VCSError:
+            log.error(traceback.format_exc())
+            log.error('Perhaps this repository is in db and not in '
+                      'filesystem run rescan repositories with '
+                      '"destroy old data " option from admin panel')
+            return
+
+        if alias == 'hg':
+
+            repo = backend(safe_str(repo_full_path), create=False,
+                           baseui=self._ui)
+            # skip hidden web repository
+            if repo._get_hidden():
+                return
+        else:
+            repo = backend(repo_full_path, create=False)
+
+        return repo
+
+
+class RepoGroup(Base, BaseModel):
+    __tablename__ = 'groups'
+    __table_args__ = (
+        UniqueConstraint('group_name', 'group_parent_id'),
+        CheckConstraint('group_id != group_parent_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    __mapper_args__ = {'order_by': 'group_name'}
+
+    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
+    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    #TODO: create this field in migrations
+    #created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    parent_group = relationship('RepoGroup', remote_side=group_id)
+    user = relationship('User')
+
+    def __init__(self, group_name='', parent_group=None):
+        self.group_name = group_name
+        self.parent_group = parent_group
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
+                                      self.group_name)
+
+    @classmethod
+    def groups_choices(cls, groups=None, show_empty_group=True):
+        from webhelpers.html import literal as _literal
+        if not groups:
+            groups = cls.query().all()
+
+        repo_groups = []
+        if show_empty_group:
+            repo_groups = [('-1', u'-- %s --' % _('top level'))]
+        sep = ' &raquo; '
+        _name = lambda k: _literal(sep.join(k))
+
+        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
+                              for x in groups])
+
+        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
+        return repo_groups
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+        if case_insensitive:
+            gr = cls.query()\
+                .filter(cls.group_name.ilike(group_name))
+        else:
+            gr = cls.query()\
+                .filter(cls.group_name == group_name)
+        if cache:
+            gr = gr.options(FromCache(
+                            "sql_cache_short",
+                            "get_group_%s" % _hash_key(group_name)
+                            )
+            )
+        return gr.scalar()
+
+    @property
+    def parents(self):
+        parents_recursion_limit = 5
+        groups = []
+        if self.parent_group is None:
+            return groups
+        cur_gr = self.parent_group
+        groups.insert(0, cur_gr)
+        cnt = 0
+        while 1:
+            cnt += 1
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            if cnt == parents_recursion_limit:
+                # this will prevent accidental infinit loops
+                log.error('group nested more than %s' %
+                          parents_recursion_limit)
+                break
+
+            groups.insert(0, gr)
+        return groups
+
+    @property
+    def children(self):
+        return RepoGroup.query().filter(RepoGroup.parent_group == self)
+
+    @property
+    def name(self):
+        return self.group_name.split(RepoGroup.url_sep())[-1]
+
+    @property
+    def full_path(self):
+        return self.group_name
+
+    @property
+    def full_path_splitted(self):
+        return self.group_name.split(RepoGroup.url_sep())
+
+    @property
+    def repositories(self):
+        return Repository.query()\
+                .filter(Repository.group == self)\
+                .order_by(Repository.repo_name)
+
+    @property
+    def repositories_recursive_count(self):
+        cnt = self.repositories.count()
+
+        def children_count(group):
+            cnt = 0
+            for child in group.children:
+                cnt += child.repositories.count()
+                cnt += children_count(child)
+            return cnt
+
+        return cnt + children_count(self)
+
+    def _recursive_objects(self, include_repos=True):
+        all_ = []
+
+        def _get_members(root_gr):
+            if include_repos:
+                for r in root_gr.repositories:
+                    all_.append(r)
+            childs = root_gr.children.all()
+            if childs:
+                for gr in childs:
+                    all_.append(gr)
+                    _get_members(gr)
+
+        _get_members(self)
+        return [self] + all_
+
+    def recursive_groups_and_repos(self):
+        """
+        Recursive return all groups, with repositories in those groups
+        """
+        return self._recursive_objects()
+
+    def recursive_groups(self):
+        """
+        Returns all children groups for this group including children of children
+        """
+        return self._recursive_objects(include_repos=False)
+
+    def get_new_name(self, group_name):
+        """
+        returns new full group name based on parent and new name
+
+        :param group_name:
+        """
+        path_prefix = (self.parent_group.full_path_splitted if
+                       self.parent_group else [])
+        return RepoGroup.url_sep().join(path_prefix + [group_name])
+
+    def get_api_data(self):
+        """
+        Common function for generating api data
+
+        """
+        group = self
+        data = dict(
+            group_id=group.group_id,
+            group_name=group.group_name,
+            group_description=group.group_description,
+            parent_group=group.parent_group.group_name if group.parent_group else None,
+            repositories=[x.repo_name for x in group.repositories],
+            owner=group.user.username
+        )
+        return data
+
+
+class Permission(Base, BaseModel):
+    __tablename__ = 'permissions'
+    __table_args__ = (
+        Index('p_perm_name_idx', 'permission_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PERMS = [
+        ('hg.admin', _('RhodeCode Administrator')),
+
+        ('repository.none', _('Repository no access')),
+        ('repository.read', _('Repository read access')),
+        ('repository.write', _('Repository write access')),
+        ('repository.admin', _('Repository admin access')),
+
+        ('group.none', _('Repository group no access')),
+        ('group.read', _('Repository group read access')),
+        ('group.write', _('Repository group write access')),
+        ('group.admin', _('Repository group admin access')),
+
+        ('usergroup.none', _('User group no access')),
+        ('usergroup.read', _('User group read access')),
+        ('usergroup.write', _('User group write access')),
+        ('usergroup.admin', _('User group admin access')),
+
+        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
+        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
+
+        ('hg.usergroup.create.false', _('User Group creation disabled')),
+        ('hg.usergroup.create.true', _('User Group creation enabled')),
+
+        ('hg.create.none', _('Repository creation disabled')),
+        ('hg.create.repository', _('Repository creation enabled')),
+
+        ('hg.fork.none', _('Repository forking disabled')),
+        ('hg.fork.repository', _('Repository forking enabled')),
+
+        ('hg.register.none', _('Registration disabled')),
+        ('hg.register.manual_activate', _('User Registration with manual account activation')),
+        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
+
+        ('hg.extern_activate.manual', _('Manual activation of external account')),
+        ('hg.extern_activate.auto', _('Automatic activation of external account')),
+
+    ]
+
+    #definition of system default permissions for DEFAULT user
+    DEFAULT_USER_PERMISSIONS = [
+        'repository.read',
+        'group.read',
+        'usergroup.read',
+        'hg.create.repository',
+        'hg.fork.repository',
+        'hg.register.manual_activate',
+        'hg.extern_activate.auto',
+    ]
+
+    # defines which permissions are more important higher the more important
+    # Weight defines which permissions are more important.
+    # The higher number the more important.
+    PERM_WEIGHTS = {
+        'repository.none': 0,
+        'repository.read': 1,
+        'repository.write': 3,
+        'repository.admin': 4,
+
+        'group.none': 0,
+        'group.read': 1,
+        'group.write': 3,
+        'group.admin': 4,
+
+        'usergroup.none': 0,
+        'usergroup.read': 1,
+        'usergroup.write': 3,
+        'usergroup.admin': 4,
+        'hg.repogroup.create.false': 0,
+        'hg.repogroup.create.true': 1,
+
+        'hg.usergroup.create.false': 0,
+        'hg.usergroup.create.true': 1,
+
+        'hg.fork.none': 0,
+        'hg.fork.repository': 1,
+        'hg.create.none': 0,
+        'hg.create.repository': 1
+    }
+
+    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__, self.permission_id, self.permission_name
+        )
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.permission_name == key).scalar()
+
+    @classmethod
+    def get_default_perms(cls, default_user_id):
+        q = Session().query(UserRepoToPerm, Repository, cls)\
+         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
+         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_group_perms(cls, default_user_id):
+        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
+         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
+         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_user_group_perms(cls, default_user_id):
+        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
+         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
+         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserUserGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+
+class UserRepoToPerm(Base, BaseModel):
+    __tablename__ = 'repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    repository = relationship('Repository')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, repository, permission):
+        n = cls()
+        n.user = user
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.repository)
+
+
+class UserUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    user_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, user_group, permission):
+        n = cls()
+        n.user = user
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.user_group)
+
+
+class UserToPerm(Base, BaseModel):
+    __tablename__ = 'user_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    permission = relationship('Permission', lazy='joined')
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.permission)
+
+
+class UserGroupRepoToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    repository = relationship('Repository')
+
+    @classmethod
+    def create(cls, users_group, repository, permission):
+        n = cls()
+        n.users_group = users_group
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
+
+
+class UserGroupUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_group_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
+        CheckConstraint('target_user_group_id != user_group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
+    user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, target_user_group, user_group, permission):
+        n = cls()
+        n.target_user_group = target_user_group
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
+
+
+class UserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'permission_id',),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+
+class UserRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    group = relationship('RepoGroup')
+    permission = relationship('Permission')
+
+
+class UserGroupRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    group = relationship('RepoGroup')
+
+
+class Statistics(Base, BaseModel):
+    __tablename__ = 'statistics'
+    __table_args__ = (
+         UniqueConstraint('repository_id'),
+         {'extend_existing': True, 'mysql_engine': 'InnoDB',
+          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
+    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
+    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
+    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
+
+    repository = relationship('Repository', single_parent=True)
+
+
+class UserFollowing(Base, BaseModel):
+    __tablename__ = 'user_followings'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'follows_repository_id'),
+        UniqueConstraint('user_id', 'follows_user_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
+    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+
+    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
+
+    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
+    follows_repository = relationship('Repository', order_by='Repository.repo_name')
+
+    @classmethod
+    def get_repo_followers(cls, repo_id):
+        return cls.query().filter(cls.follows_repo_id == repo_id)
+
+
+class CacheInvalidation(Base, BaseModel):
+    __tablename__ = 'cache_invalidation'
+    __table_args__ = (
+        UniqueConstraint('cache_key'),
+        Index('key_idx', 'cache_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    # cache_id, not used
+    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    # cache_key as created by _get_cache_key
+    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    # cache_args is a repo_name
+    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    # instance sets cache_active True when it is caching,
+    # other instances set cache_active to False to indicate that this cache is invalid
+    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+
+    def __init__(self, cache_key, repo_name=''):
+        self.cache_key = cache_key
+        self.cache_args = repo_name
+        self.cache_active = False
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
+                            self.cache_id, self.cache_key, self.cache_active)
+
+    def _cache_key_partition(self):
+        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
+        return prefix, repo_name, suffix
+
+    def get_prefix(self):
+        """
+        get prefix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[0]
+
+    def get_suffix(self):
+        """
+        get suffix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[2]
+
+    @classmethod
+    def clear_cache(cls):
+        """
+        Delete all cache keys from database.
+        Should only be run when all instances are down and all entries thus stale.
+        """
+        cls.query().delete()
+        Session().commit()
+
+    @classmethod
+    def _get_cache_key(cls, key):
+        """
+        Wrapper for generating a unique cache key for this instance and "key".
+        key must / will start with a repo_name which will be stored in .cache_args .
+        """
+        import rhodecode
+        prefix = rhodecode.CONFIG.get('instance_id', '')
+        return "%s%s" % (prefix, key)
+
+    @classmethod
+    def set_invalidate(cls, repo_name, delete=False):
+        """
+        Mark all caches of a repo as invalid in the database.
+        """
+        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
+
+        try:
+            for inv_obj in inv_objs:
+                log.debug('marking %s key for invalidation based on repo_name=%s'
+                          % (inv_obj, safe_str(repo_name)))
+                if delete:
+                    Session().delete(inv_obj)
+                else:
+                    inv_obj.cache_active = False
+                    Session().add(inv_obj)
+            Session().commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+
+    @classmethod
+    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
+        """
+        Mark this cache key as active and currently cached.
+        Return True if the existing cache registration still was valid.
+        Return False to indicate that it had been invalidated and caches should be refreshed.
+        """
+
+        key = (repo_name + '_' + kind) if kind else repo_name
+        cache_key = cls._get_cache_key(key)
+
+        if valid_cache_keys and cache_key in valid_cache_keys:
+            return True
+
+        try:
+            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
+            if not inv_obj:
+                inv_obj = CacheInvalidation(cache_key, repo_name)
+            was_valid = inv_obj.cache_active
+            inv_obj.cache_active = True
+            Session().add(inv_obj)
+            Session().commit()
+            return was_valid
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+            return False
+
+    @classmethod
+    def get_valid_cache_keys(cls):
+        """
+        Return opaque object with information of which caches still are valid
+        and can be used without checking for invalidation.
+        """
+        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
+
+
+class ChangesetComment(Base, BaseModel):
+    __tablename__ = 'changeset_comments'
+    __table_args__ = (
+        Index('cc_revision_idx', 'revision'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    revision = Column('revision', String(40), nullable=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+    line_no = Column('line_no', Unicode(10), nullable=True)
+    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
+    f_path = Column('f_path', Unicode(1000), nullable=True)
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+    text = Column('text', UnicodeText(25000), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    @classmethod
+    def get_users(cls, revision=None, pull_request_id=None):
+        """
+        Returns user associated with this ChangesetComment. ie those
+        who actually commented
+
+        :param cls:
+        :param revision:
+        """
+        q = Session().query(User)\
+                .join(ChangesetComment.author)
+        if revision:
+            q = q.filter(cls.revision == revision)
+        elif pull_request_id:
+            q = q.filter(cls.pull_request_id == pull_request_id)
+        return q.all()
+
+
+class ChangesetStatus(Base, BaseModel):
+    __tablename__ = 'changeset_statuses'
+    __table_args__ = (
+        Index('cs_revision_idx', 'revision'),
+        Index('cs_version_idx', 'version'),
+        UniqueConstraint('repo_id', 'revision', 'version'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
+    STATUS_APPROVED = 'approved'
+    STATUS_REJECTED = 'rejected'
+    STATUS_UNDER_REVIEW = 'under_review'
+
+    STATUSES = [
+        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
+        (STATUS_APPROVED, _("Approved")),
+        (STATUS_REJECTED, _("Rejected")),
+        (STATUS_UNDER_REVIEW, _("Under Review")),
+    ]
+
+    changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    revision = Column('revision', String(40), nullable=False)
+    status = Column('status', String(128), nullable=False, default=DEFAULT)
+    changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
+    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
+    version = Column('version', Integer(), nullable=False, default=0)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    comment = relationship('ChangesetComment', lazy='joined')
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__,
+            self.status, self.author
+        )
+
+    @classmethod
+    def get_status_lbl(cls, value):
+        return dict(cls.STATUSES).get(value)
+
+    @property
+    def status_lbl(self):
+        return ChangesetStatus.get_status_lbl(self.status)
+
+
+class PullRequest(Base, BaseModel):
+    __tablename__ = 'pull_requests'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    # values for .status
+    STATUS_NEW = u'new'
+    STATUS_OPEN = u'open'
+    STATUS_CLOSED = u'closed'
+
+    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
+    title = Column('title', Unicode(256), nullable=True)
+    description = Column('description', UnicodeText(10240), nullable=True)
+    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    _revisions = Column('revisions', UnicodeText(20500))  # 500 revisions max
+    org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    org_ref = Column('org_ref', Unicode(256), nullable=False)
+    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    other_ref = Column('other_ref', Unicode(256), nullable=False)
+
+    @hybrid_property
+    def revisions(self):
+        return self._revisions.split(':')
+
+    @revisions.setter
+    def revisions(self, val):
+        self._revisions = ':'.join(val)
+
+    @property
+    def org_ref_parts(self):
+        return self.org_ref.split(':')
+
+    @property
+    def other_ref_parts(self):
+        return self.other_ref.split(':')
+
+    author = relationship('User', lazy='joined')
+    reviewers = relationship('PullRequestReviewers',
+                             cascade="all, delete, delete-orphan")
+    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
+    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
+    statuses = relationship('ChangesetStatus')
+    comments = relationship('ChangesetComment',
+                             cascade="all, delete, delete-orphan")
+
+    def is_closed(self):
+        return self.status == self.STATUS_CLOSED
+
+    @property
+    def last_review_status(self):
+        return self.statuses[-1].status if self.statuses else ''
+
+    def __json__(self):
+        return dict(
+            revisions=self.revisions
+        )
+
+
+class PullRequestReviewers(Base, BaseModel):
+    __tablename__ = 'pull_request_reviewers'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    def __init__(self, user=None, pull_request=None):
+        self.user = user
+        self.pull_request = pull_request
+
+    pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
+
+    user = relationship('User')
+    pull_request = relationship('PullRequest')
+
+
+class Notification(Base, BaseModel):
+    __tablename__ = 'notifications'
+    __table_args__ = (
+        Index('notification_type_idx', 'type'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    TYPE_CHANGESET_COMMENT = u'cs_comment'
+    TYPE_MESSAGE = u'message'
+    TYPE_MENTION = u'mention'
+    TYPE_REGISTRATION = u'registration'
+    TYPE_PULL_REQUEST = u'pull_request'
+    TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
+
+    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
+    subject = Column('subject', Unicode(512), nullable=True)
+    body = Column('body', UnicodeText(50000), nullable=True)
+    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    type_ = Column('type', Unicode(256))
+
+    created_by_user = relationship('User')
+    notifications_to_users = relationship('UserNotification', lazy='joined',
+                                          cascade="all, delete, delete-orphan")
+
+    @property
+    def recipients(self):
+        return [x.user for x in UserNotification.query()\
+                .filter(UserNotification.notification == self)\
+                .order_by(UserNotification.user_id.asc()).all()]
+
+    @classmethod
+    def create(cls, created_by, subject, body, recipients, type_=None):
+        if type_ is None:
+            type_ = Notification.TYPE_MESSAGE
+
+        notification = cls()
+        notification.created_by_user = created_by
+        notification.subject = subject
+        notification.body = body
+        notification.type_ = type_
+        notification.created_on = datetime.datetime.now()
+
+        for u in recipients:
+            assoc = UserNotification()
+            assoc.notification = notification
+            u.notifications.append(assoc)
+        Session().add(notification)
+        return notification
+
+    @property
+    def description(self):
+        from rhodecode.model.notification import NotificationModel
+        return NotificationModel().make_description(self)
+
+
+class UserNotification(Base, BaseModel):
+    __tablename__ = 'user_to_notification'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'notification_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
+    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
+    read = Column('read', Boolean, default=False)
+    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
+
+    user = relationship('User', lazy="joined")
+    notification = relationship('Notification', lazy="joined",
+                                order_by=lambda: Notification.created_on.desc(),)
+
+    def mark_as_read(self):
+        self.read = True
+        Session().add(self)
+
+
+class Gist(Base, BaseModel):
+    __tablename__ = 'gists'
+    __table_args__ = (
+        Index('g_gist_access_id_idx', 'gist_access_id'),
+        Index('g_created_on_idx', 'created_on'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    GIST_PUBLIC = u'public'
+    GIST_PRIVATE = u'private'
+
+    gist_id = Column('gist_id', Integer(), primary_key=True)
+    gist_access_id = Column('gist_access_id', Unicode(250))
+    gist_description = Column('gist_description', UnicodeText(1024))
+    gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
+    gist_expires = Column('gist_expires', Float(53), nullable=False)
+    gist_type = Column('gist_type', Unicode(128), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    owner = relationship('User')
+
+    @classmethod
+    def get_or_404(cls, id_):
+        res = cls.query().filter(cls.gist_access_id == id_).scalar()
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def get_by_access_id(cls, gist_access_id):
+        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
+
+    def gist_url(self):
+        import rhodecode
+        alias_url = rhodecode.CONFIG.get('gist_alias_url')
+        if alias_url:
+            return alias_url.replace('{gistid}', self.gist_access_id)
+
+        from pylons import url
+        return url('gist', gist_id=self.gist_access_id, qualified=True)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all gists are stored
+
+        :param cls:
+        """
+        from rhodecode.model.gist import GIST_STORE_LOC
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == URL_SEP)
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
+
+    def get_api_data(self):
+        """
+        Common function for generating gist related data for API
+        """
+        gist = self
+        data = dict(
+            gist_id=gist.gist_id,
+            type=gist.gist_type,
+            access_id=gist.gist_access_id,
+            description=gist.gist_description,
+            url=gist.gist_url(),
+            expires=gist.gist_expires,
+            created_on=gist.created_on,
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+        )
+        data.update(self.get_api_data())
+        return data
+    ## SCM functions
+
+    @property
+    def scm_instance(self):
+        from rhodecode.lib.vcs import get_repo
+        base_path = self.base_path()
+        return get_repo(os.path.join(*map(safe_str,
+                                          [base_path, self.gist_access_id])))
+
+
+class DbMigrateVersion(Base, BaseModel):
+    __tablename__ = 'db_migrate_version'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    repository_id = Column('repository_id', String(250), primary_key=True)
+    repository_path = Column('repository_path', Text)
+    version = Column('version', Integer)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/schema/db_2_0_2.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,2352 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db
+~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import os
+import time
+import logging
+import datetime
+import traceback
+import hashlib
+import collections
+import functools
+
+from sqlalchemy import *
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
+from sqlalchemy.exc import DatabaseError
+from beaker.cache import cache_region, region_invalidate
+from webob.exc import HTTPNotFound
+
+from pylons.i18n.translation import lazy_ugettext as _
+
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
+
+from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
+    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int
+from rhodecode.lib.compat import json
+from rhodecode.lib.caching_query import FromCache
+
+from rhodecode.model.meta import Base, Session
+
+URL_SEP = '/'
+log = logging.getLogger(__name__)
+
+#==============================================================================
+# BASE CLASSES
+#==============================================================================
+
+_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
+
+
+class BaseModel(object):
+    """
+    Base Model for all classess
+    """
+
+    @classmethod
+    def _get_keys(cls):
+        """return column names for this model """
+        return class_mapper(cls).c.keys()
+
+    def get_dict(self):
+        """
+        return dict with keys and values corresponding
+        to this model data """
+
+        d = {}
+        for k in self._get_keys():
+            d[k] = getattr(self, k)
+
+        # also use __json__() if present to get additional fields
+        _json_attr = getattr(self, '__json__', None)
+        if _json_attr:
+            # update with attributes from __json__
+            if callable(_json_attr):
+                _json_attr = _json_attr()
+            for k, val in _json_attr.iteritems():
+                d[k] = val
+        return d
+
+    def get_appstruct(self):
+        """return list with keys and values tupples corresponding
+        to this model data """
+
+        l = []
+        for k in self._get_keys():
+            l.append((k, getattr(self, k),))
+        return l
+
+    def populate_obj(self, populate_dict):
+        """populate model with data from given populate_dict"""
+
+        for k in self._get_keys():
+            if k in populate_dict:
+                setattr(self, k, populate_dict[k])
+
+    @classmethod
+    def query(cls):
+        return Session().query(cls)
+
+    @classmethod
+    def get(cls, id_):
+        if id_:
+            return cls.query().get(id_)
+
+    @classmethod
+    def get_or_404(cls, id_):
+        try:
+            id_ = int(id_)
+        except (TypeError, ValueError):
+            raise HTTPNotFound
+
+        res = cls.query().get(id_)
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def getAll(cls):
+        # deprecated and left for backward compatibility
+        return cls.get_all()
+
+    @classmethod
+    def get_all(cls):
+        return cls.query().all()
+
+    @classmethod
+    def delete(cls, id_):
+        obj = cls.query().get(id_)
+        Session().delete(obj)
+
+    def __repr__(self):
+        if hasattr(self, '__unicode__'):
+            # python repr needs to return str
+            try:
+                return safe_str(self.__unicode__())
+            except UnicodeDecodeError:
+                pass
+        return '<DB:%s>' % (self.__class__.__name__)
+
+
+class RhodeCodeSetting(Base, BaseModel):
+    SETTINGS_TYPES = {
+        'str': safe_str,
+        'int': safe_int,
+        'unicode': safe_unicode,
+        'bool': str2bool,
+        'list': functools.partial(aslist, sep=',')
+    }
+    __tablename__ = 'rhodecode_settings'
+    __table_args__ = (
+        UniqueConstraint('app_settings_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __init__(self, key='', val='', type='unicode'):
+        self.app_settings_name = key
+        self.app_settings_value = val
+        self.app_settings_type = type
+
+    @validates('_app_settings_value')
+    def validate_settings_value(self, key, val):
+        assert type(val) == unicode
+        return val
+
+    @hybrid_property
+    def app_settings_value(self):
+        v = self._app_settings_value
+        _type = self.app_settings_type
+        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
+        return converter(v)
+
+    @app_settings_value.setter
+    def app_settings_value(self, val):
+        """
+        Setter that will always make sure we use unicode in app_settings_value
+
+        :param val:
+        """
+        self._app_settings_value = safe_unicode(val)
+
+    @hybrid_property
+    def app_settings_type(self):
+        return self._app_settings_type
+
+    @app_settings_type.setter
+    def app_settings_type(self, val):
+        if val not in self.SETTINGS_TYPES:
+            raise Exception('type must be one of %s got %s'
+                            % (self.SETTINGS_TYPES.keys(), val))
+        self._app_settings_type = val
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (
+            self.__class__.__name__,
+            self.app_settings_name, self.app_settings_value, self.app_settings_type
+        )
+
+    @classmethod
+    def get_by_name(cls, key):
+        return cls.query()\
+            .filter(cls.app_settings_name == key).scalar()
+
+    @classmethod
+    def get_by_name_or_create(cls, key, val='', type='unicode'):
+        res = cls.get_by_name(key)
+        if not res:
+            res = cls(key, val, type)
+        return res
+
+    @classmethod
+    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
+        """
+        Creates or updates RhodeCode setting. If updates is triggered it will only
+        update parameters that are explicityl set Optional instance will be skipped
+
+        :param key:
+        :param val:
+        :param type:
+        :return:
+        """
+        res = cls.get_by_name(key)
+        if not res:
+            val = Optional.extract(val)
+            type = Optional.extract(type)
+            res = cls(key, val, type)
+        else:
+            res.app_settings_name = key
+            if not isinstance(val, Optional):
+                # update if set
+                res.app_settings_value = val
+            if not isinstance(type, Optional):
+                # update if set
+                res.app_settings_type = type
+        return res
+
+    @classmethod
+    def get_app_settings(cls, cache=False):
+
+        ret = cls.query()
+
+        if cache:
+            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
+
+        if not ret:
+            raise Exception('Could not get application settings !')
+        settings = {}
+        for each in ret:
+            settings['rhodecode_' + each.app_settings_name] = \
+                each.app_settings_value
+
+        return settings
+
+    @classmethod
+    def get_auth_plugins(cls, cache=False):
+        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
+        return auth_plugins
+
+    @classmethod
+    def get_auth_settings(cls, cache=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('auth_')).all()
+        fd = {}
+        for row in ret:
+            fd.update({row.app_settings_name: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('default_')).all()
+        fd = {}
+        for row in ret:
+            key = row.app_settings_name
+            if strip_prefix:
+                key = remove_prefix(key, prefix='default_')
+            fd.update({key: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_server_info(cls):
+        import pkg_resources
+        import platform
+        import rhodecode
+        from rhodecode.lib.utils import check_git_version
+        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
+        info = {
+            'modules': sorted(mods, key=lambda k: k[0].lower()),
+            'py_version': platform.python_version(),
+            'platform': safe_unicode(platform.platform()),
+            'rhodecode_version': rhodecode.__version__,
+            'git_version': safe_unicode(check_git_version()),
+            'git_path': rhodecode.CONFIG.get('git_path')
+        }
+        return info
+
+
+class RhodeCodeUi(Base, BaseModel):
+    __tablename__ = 'rhodecode_ui'
+    __table_args__ = (
+        UniqueConstraint('ui_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    HOOK_UPDATE = 'changegroup.update'
+    HOOK_REPO_SIZE = 'changegroup.repo_size'
+    HOOK_PUSH = 'changegroup.push_logger'
+    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
+    HOOK_PULL = 'outgoing.pull_logger'
+    HOOK_PRE_PULL = 'preoutgoing.pre_pull'
+
+    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
+
+    # def __init__(self, section='', key='', value=''):
+    #     self.ui_section = section
+    #     self.ui_key = key
+    #     self.ui_value = value
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.ui_key == key).scalar()
+
+    @classmethod
+    def get_builtin_hooks(cls):
+        q = cls.query()
+        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        return q.all()
+
+    @classmethod
+    def get_custom_hooks(cls):
+        q = cls.query()
+        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        q = q.filter(cls.ui_section == 'hooks')
+        return q.all()
+
+    @classmethod
+    def get_repos_location(cls):
+        return cls.get_by_key('/').ui_value
+
+    @classmethod
+    def create_or_update_hook(cls, key, val):
+        new_ui = cls.get_by_key(key) or cls()
+        new_ui.ui_section = 'hooks'
+        new_ui.ui_active = True
+        new_ui.ui_key = key
+        new_ui.ui_value = val
+
+        Session().add(new_ui)
+
+    def __repr__(self):
+        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
+                                   self.ui_value)
+
+
+class User(Base, BaseModel):
+    __tablename__ = 'users'
+    __table_args__ = (
+        UniqueConstraint('username'), UniqueConstraint('email'),
+        Index('u_username_idx', 'username'),
+        Index('u_email_idx', 'email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    DEFAULT_USER = 'default'
+
+    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
+    extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    user_log = relationship('UserLog')
+    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
+
+    repositories = relationship('Repository')
+    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
+    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
+
+    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
+
+    group_member = relationship('UserGroupMember', cascade='all')
+
+    notifications = relationship('UserNotification', cascade='all')
+    # notifications assigned to this user
+    user_created_notifications = relationship('Notification', cascade='all')
+    # comments created by this user
+    user_comments = relationship('ChangesetComment', cascade='all')
+    #extra emails for this user
+    user_emails = relationship('UserEmailMap', cascade='all')
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+    @property
+    def firstname(self):
+        # alias for future
+        return self.name
+
+    @property
+    def emails(self):
+        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
+        return [self.email] + [x.email for x in other]
+
+    @property
+    def ip_addresses(self):
+        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
+        return [x.ip_addr for x in ret]
+
+    @property
+    def username_and_name(self):
+        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
+
+    @property
+    def full_name(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def full_name_or_username(self):
+        return ('%s %s' % (self.firstname, self.lastname)
+                if (self.firstname and self.lastname) else self.username)
+
+    @property
+    def full_contact(self):
+        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
+
+    @property
+    def short_contact(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def is_admin(self):
+        return self.admin
+
+    @property
+    def AuthUser(self):
+        """
+        Returns instance of AuthUser for this user
+        """
+        from rhodecode.lib.auth import AuthUser
+        return AuthUser(user_id=self.user_id, api_key=self.api_key,
+                        username=self.username)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.user_id, self.username)
+
+    @classmethod
+    def get_by_username(cls, username, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.username.ilike(username))
+        else:
+            q = cls.query().filter(cls.username == username)
+
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(username)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get_by_api_key(cls, api_key, cache=False):
+        q = cls.query().filter(cls.api_key == api_key)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_api_key_%s" % api_key))
+        return q.scalar()
+
+    @classmethod
+    def get_by_email(cls, email, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.email.ilike(email))
+        else:
+            q = cls.query().filter(cls.email == email)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_email_key_%s" % email))
+
+        ret = q.scalar()
+        if ret is None:
+            q = UserEmailMap.query()
+            # try fetching in alternate email map
+            if case_insensitive:
+                q = q.filter(UserEmailMap.email.ilike(email))
+            else:
+                q = q.filter(UserEmailMap.email == email)
+            q = q.options(joinedload(UserEmailMap.user))
+            if cache:
+                q = q.options(FromCache("sql_cache_short",
+                                        "get_email_map_key_%s" % email))
+            ret = getattr(q.scalar(), 'user', None)
+
+        return ret
+
+    @classmethod
+    def get_from_cs_author(cls, author):
+        """
+        Tries to get User objects out of commit author string
+
+        :param author:
+        """
+        from rhodecode.lib.helpers import email, author_name
+        # Valid email in the attribute passed, see if they're in the system
+        _email = email(author)
+        if _email:
+            user = cls.get_by_email(_email, case_insensitive=True)
+            if user:
+                return user
+        # Maybe we can match by username?
+        _author = author_name(author)
+        user = cls.get_by_username(_author, case_insensitive=True)
+        if user:
+            return user
+
+    def update_lastlogin(self):
+        """Update user lastlogin"""
+        self.last_login = datetime.datetime.now()
+        Session().add(self)
+        log.debug('updated user %s lastlogin' % self.username)
+
+    @classmethod
+    def get_first_admin(cls):
+        user = User.query().filter(User.admin == True).first()
+        if user is None:
+            raise Exception('Missing administrative account!')
+        return user
+
+    @classmethod
+    def get_default_user(cls, cache=False):
+        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
+        if user is None:
+            raise Exception('Missing default account!')
+        return user
+
+    def get_api_data(self):
+        """
+        Common function for generating user related data for API
+        """
+        user = self
+        data = dict(
+            user_id=user.user_id,
+            username=user.username,
+            firstname=user.name,
+            lastname=user.lastname,
+            email=user.email,
+            emails=user.emails,
+            api_key=user.api_key,
+            active=user.active,
+            admin=user.admin,
+            extern_type=user.extern_type,
+            extern_name=user.extern_name,
+            last_login=user.last_login,
+            ip_addresses=user.ip_addresses
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+            full_name=self.full_name,
+            full_name_or_username=self.full_name_or_username,
+            short_contact=self.short_contact,
+            full_contact=self.full_contact
+        )
+        data.update(self.get_api_data())
+        return data
+
+
+class UserEmailMap(Base, BaseModel):
+    __tablename__ = 'user_email_map'
+    __table_args__ = (
+        Index('uem_email_idx', 'email'),
+        UniqueConstraint('email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    user = relationship('User', lazy='joined')
+
+    @validates('_email')
+    def validate_email(self, key, email):
+        # check if this email is not main one
+        main_email = Session().query(User).filter(User.email == email).scalar()
+        if main_email is not None:
+            raise AttributeError('email %s is present is user table' % email)
+        return email
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+
+class UserIpMap(Base, BaseModel):
+    __tablename__ = 'user_ip_map'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'ip_addr'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    user = relationship('User', lazy='joined')
+
+    @classmethod
+    def _get_ip_range(cls, ip_addr):
+        from rhodecode.lib import ipaddr
+        net = ipaddr.IPNetwork(address=ip_addr)
+        return [str(net.network), str(net.broadcast)]
+
+    def __json__(self):
+        return dict(
+          ip_addr=self.ip_addr,
+          ip_range=self._get_ip_range(self.ip_addr)
+        )
+
+
+class UserLog(Base, BaseModel):
+    __tablename__ = 'user_logs'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.repository_name,
+                                      self.action)
+
+    @property
+    def action_as_day(self):
+        return datetime.date(*self.action_date.timetuple()[:3])
+
+    user = relationship('User')
+    repository = relationship('Repository', cascade='')
+
+
+class UserGroup(Base, BaseModel):
+    __tablename__ = 'users_groups'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
+    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
+    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
+    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
+
+    user = relationship('User')
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.users_group_id,
+                                      self.users_group_name)
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False,
+                          case_insensitive=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.users_group_name.ilike(group_name))
+        else:
+            q = cls.query().filter(cls.users_group_name == group_name)
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(group_name)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get(cls, user_group_id, cache=False):
+        user_group = cls.query()
+        if cache:
+            user_group = user_group.options(FromCache("sql_cache_short",
+                                    "get_users_group_%s" % user_group_id))
+        return user_group.get(user_group_id)
+
+    def get_api_data(self, with_members=True):
+        user_group = self
+
+        data = dict(
+            users_group_id=user_group.users_group_id,
+            group_name=user_group.users_group_name,
+            group_description=user_group.user_group_description,
+            active=user_group.users_group_active,
+            owner=user_group.user.username,
+        )
+        if with_members:
+            members = []
+            for user in user_group.members:
+                user = user.user
+                members.append(user.get_api_data())
+            data['members'] = members
+
+        return data
+
+
+class UserGroupMember(Base, BaseModel):
+    __tablename__ = 'users_groups_members'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User', lazy='joined')
+    users_group = relationship('UserGroup')
+
+    def __init__(self, gr_id='', u_id=''):
+        self.users_group_id = gr_id
+        self.user_id = u_id
+
+
+class RepositoryField(Base, BaseModel):
+    __tablename__ = 'repositories_fields'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PREFIX = 'ex_'  # prefix used in form to not conflict with already existing fields
+
+    repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
+    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_type = Column("field_type", String(256), nullable=False, unique=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repository = relationship('Repository')
+
+    @property
+    def field_key_prefixed(self):
+        return 'ex_%s' % self.field_key
+
+    @classmethod
+    def un_prefix_key(cls, key):
+        if key.startswith(cls.PREFIX):
+            return key[len(cls.PREFIX):]
+        return key
+
+    @classmethod
+    def get_by_key_name(cls, key, repo):
+        row = cls.query()\
+                .filter(cls.repository == repo)\
+                .filter(cls.field_key == key).scalar()
+        return row
+
+
+class Repository(Base, BaseModel):
+    __tablename__ = 'repositories'
+    __table_args__ = (
+        UniqueConstraint('repo_name'),
+        Index('r_repo_name_idx', 'repo_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
+    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
+    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
+    description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    _landing_revision = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
+
+    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
+
+    user = relationship('User')
+    fork = relationship('Repository', remote_side=repo_id)
+    group = relationship('RepoGroup')
+    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    stats = relationship('Statistics', cascade='all', uselist=False)
+
+    followers = relationship('UserFollowing',
+                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
+                             cascade='all')
+    extra_fields = relationship('RepositoryField',
+                                cascade="all, delete, delete-orphan")
+
+    logs = relationship('UserLog')
+    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
+
+    pull_requests_org = relationship('PullRequest',
+                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    pull_requests_other = relationship('PullRequest',
+                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
+                                   safe_unicode(self.repo_name))
+
+    @hybrid_property
+    def landing_rev(self):
+        # always should return [rev_type, rev]
+        if self._landing_revision:
+            _rev_info = self._landing_revision.split(':')
+            if len(_rev_info) < 2:
+                _rev_info.insert(0, 'rev')
+            return [_rev_info[0], _rev_info[1]]
+        return [None, None]
+
+    @landing_rev.setter
+    def landing_rev(self, val):
+        if ':' not in val:
+            raise ValueError('value must be delimited with `:` and consist '
+                             'of <rev_type>:<rev>, got %s instead' % val)
+        self._landing_revision = val
+
+    @hybrid_property
+    def locked(self):
+        # always should return [user_id, timelocked]
+        if self._locked:
+            _lock_info = self._locked.split(':')
+            return int(_lock_info[0]), _lock_info[1]
+        return [None, None]
+
+    @locked.setter
+    def locked(self, val):
+        if val and isinstance(val, (list, tuple)):
+            self._locked = ':'.join(map(str, val))
+        else:
+            self._locked = None
+
+    @hybrid_property
+    def changeset_cache(self):
+        from rhodecode.lib.vcs.backends.base import EmptyChangeset
+        dummy = EmptyChangeset().__json__()
+        if not self._changeset_cache:
+            return dummy
+        try:
+            return json.loads(self._changeset_cache)
+        except TypeError:
+            return dummy
+
+    @changeset_cache.setter
+    def changeset_cache(self, val):
+        try:
+            self._changeset_cache = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def normalize_repo_name(cls, repo_name):
+        """
+        Normalizes os specific repo_name to the format internally stored inside
+        dabatabase using URL_SEP
+
+        :param cls:
+        :param repo_name:
+        """
+        return cls.url_sep().join(repo_name.split(os.sep))
+
+    @classmethod
+    def get_by_repo_name(cls, repo_name):
+        q = Session().query(cls).filter(cls.repo_name == repo_name)
+        q = q.options(joinedload(Repository.fork))\
+                .options(joinedload(Repository.user))\
+                .options(joinedload(Repository.group))
+        return q.scalar()
+
+    @classmethod
+    def get_by_full_path(cls, repo_full_path):
+        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
+        repo_name = cls.normalize_repo_name(repo_name)
+        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
+
+    @classmethod
+    def get_repo_forks(cls, repo_id):
+        return cls.query().filter(Repository.fork_id == repo_id)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all repos are stored
+
+        :param cls:
+        """
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == cls.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def forks(self):
+        """
+        Return forks of this repo
+        """
+        return Repository.get_repo_forks(self.repo_id)
+
+    @property
+    def parent(self):
+        """
+        Returns fork parent
+        """
+        return self.fork
+
+    @property
+    def just_name(self):
+        return self.repo_name.split(Repository.url_sep())[-1]
+
+    @property
+    def groups_with_parents(self):
+        groups = []
+        if self.group is None:
+            return groups
+
+        cur_gr = self.group
+        groups.insert(0, cur_gr)
+        while 1:
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            groups.insert(0, gr)
+
+        return groups
+
+    @property
+    def groups_and_repo(self):
+        return self.groups_with_parents, self.just_name, self.repo_name
+
+    @LazyProperty
+    def repo_path(self):
+        """
+        Returns base full path for that repository means where it actually
+        exists on a filesystem
+        """
+        q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+                                              Repository.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def repo_full_path(self):
+        p = [self.repo_path]
+        # we need to split the name by / since this is how we store the
+        # names in the database, but that eventually needs to be converted
+        # into a valid system path
+        p += self.repo_name.split(Repository.url_sep())
+        return os.path.join(*map(safe_unicode, p))
+
+    @property
+    def cache_keys(self):
+        """
+        Returns associated cache keys for that repo
+        """
+        return CacheInvalidation.query()\
+            .filter(CacheInvalidation.cache_args == self.repo_name)\
+            .order_by(CacheInvalidation.cache_key)\
+            .all()
+
+    def get_new_name(self, repo_name):
+        """
+        returns new full repository name based on assigned group and new new
+
+        :param group_name:
+        """
+        path_prefix = self.group.full_path_splitted if self.group else []
+        return Repository.url_sep().join(path_prefix + [repo_name])
+
+    @property
+    def _ui(self):
+        """
+        Creates an db based ui object for this repository
+        """
+        from rhodecode.lib.utils import make_ui
+        return make_ui('db', clear_session=False)
+
+    @classmethod
+    def is_valid(cls, repo_name):
+        """
+        returns True if given repo name is a valid filesystem repository
+
+        :param cls:
+        :param repo_name:
+        """
+        from rhodecode.lib.utils import is_valid_repo
+
+        return is_valid_repo(repo_name, cls.base_path())
+
+    def get_api_data(self):
+        """
+        Common function for generating repo api data
+
+        """
+        repo = self
+        data = dict(
+            repo_id=repo.repo_id,
+            repo_name=repo.repo_name,
+            repo_type=repo.repo_type,
+            clone_uri=repo.clone_uri,
+            private=repo.private,
+            created_on=repo.created_on,
+            description=repo.description,
+            landing_rev=repo.landing_rev,
+            owner=repo.user.username,
+            fork_of=repo.fork.repo_name if repo.fork else None,
+            enable_statistics=repo.enable_statistics,
+            enable_locking=repo.enable_locking,
+            enable_downloads=repo.enable_downloads,
+            last_changeset=repo.changeset_cache,
+            locked_by=User.get(self.locked[0]).get_api_data() \
+                if self.locked[0] else None,
+            locked_date=time_to_datetime(self.locked[1]) \
+                if self.locked[1] else None
+        )
+        rc_config = RhodeCodeSetting.get_app_settings()
+        repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
+        if repository_fields:
+            for f in self.extra_fields:
+                data[f.field_key_prefixed] = f.field_value
+
+        return data
+
+    @classmethod
+    def lock(cls, repo, user_id, lock_time=None):
+        if not lock_time:
+            lock_time = time.time()
+        repo.locked = [user_id, lock_time]
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def unlock(cls, repo):
+        repo.locked = None
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def getlock(cls, repo):
+        return repo.locked
+
+    @property
+    def last_db_change(self):
+        return self.updated_on
+
+    def clone_url(self, **override):
+        from pylons import url
+        from urlparse import urlparse
+        import urllib
+        parsed_url = urlparse(url('home', qualified=True))
+        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
+        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
+        args = {
+           'user': '',
+           'pass': '',
+           'scheme': parsed_url.scheme,
+           'netloc': parsed_url.netloc,
+           'prefix': decoded_path,
+           'path': self.repo_name
+        }
+
+        args.update(override)
+        return default_clone_uri % args
+
+    #==========================================================================
+    # SCM PROPERTIES
+    #==========================================================================
+
+    def get_changeset(self, rev=None):
+        return get_changeset_safe(self.scm_instance, rev)
+
+    def get_landing_changeset(self):
+        """
+        Returns landing changeset, or if that doesn't exist returns the tip
+        """
+        _rev_type, _rev = self.landing_rev
+        cs = self.get_changeset(_rev)
+        if isinstance(cs, EmptyChangeset):
+            return self.get_changeset()
+        return cs
+
+    def update_changeset_cache(self, cs_cache=None):
+        """
+        Update cache of last changeset for repository, keys should be::
+
+            short_id
+            raw_id
+            revision
+            message
+            date
+            author
+
+        :param cs_cache:
+        """
+        from rhodecode.lib.vcs.backends.base import BaseChangeset
+        if cs_cache is None:
+            cs_cache = EmptyChangeset()
+            # use no-cache version here
+            scm_repo = self.scm_instance_no_cache()
+            if scm_repo:
+                cs_cache = scm_repo.get_changeset()
+
+        if isinstance(cs_cache, BaseChangeset):
+            cs_cache = cs_cache.__json__()
+
+        if (cs_cache != self.changeset_cache or not self.changeset_cache):
+            _default = datetime.datetime.fromtimestamp(0)
+            last_change = cs_cache.get('date') or _default
+            log.debug('updated repo %s with new cs cache %s'
+                      % (self.repo_name, cs_cache))
+            self.updated_on = last_change
+            self.changeset_cache = cs_cache
+            Session().add(self)
+            Session().commit()
+        else:
+            log.debug('Skipping repo:%s already with latest changes'
+                      % self.repo_name)
+
+    @property
+    def tip(self):
+        return self.get_changeset('tip')
+
+    @property
+    def author(self):
+        return self.tip.author
+
+    @property
+    def last_change(self):
+        return self.scm_instance.last_change
+
+    def get_comments(self, revisions=None):
+        """
+        Returns comments for this repository grouped by revisions
+
+        :param revisions: filter query by revisions only
+        """
+        cmts = ChangesetComment.query()\
+            .filter(ChangesetComment.repo == self)
+        if revisions:
+            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
+        grouped = collections.defaultdict(list)
+        for cmt in cmts.all():
+            grouped[cmt.revision].append(cmt)
+        return grouped
+
+    def statuses(self, revisions=None):
+        """
+        Returns statuses for this repository
+
+        :param revisions: list of revisions to get statuses for
+        """
+
+        statuses = ChangesetStatus.query()\
+            .filter(ChangesetStatus.repo == self)\
+            .filter(ChangesetStatus.version == 0)
+        if revisions:
+            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
+        grouped = {}
+
+        #maybe we have open new pullrequest without a status ?
+        stat = ChangesetStatus.STATUS_UNDER_REVIEW
+        status_lbl = ChangesetStatus.get_status_lbl(stat)
+        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
+            for rev in pr.revisions:
+                pr_id = pr.pull_request_id
+                pr_repo = pr.other_repo.repo_name
+                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
+
+        for stat in statuses.all():
+            pr_id = pr_repo = None
+            if stat.pull_request:
+                pr_id = stat.pull_request.pull_request_id
+                pr_repo = stat.pull_request.other_repo.repo_name
+            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
+                                      pr_id, pr_repo]
+        return grouped
+
+    def _repo_size(self):
+        from rhodecode.lib import helpers as h
+        log.debug('calculating repository size...')
+        return h.format_byte_size(self.scm_instance.size)
+
+    #==========================================================================
+    # SCM CACHE INSTANCE
+    #==========================================================================
+
+    def set_invalidate(self):
+        """
+        Mark caches of this repo as invalid.
+        """
+        CacheInvalidation.set_invalidate(self.repo_name)
+
+    def scm_instance_no_cache(self):
+        return self.__get_instance()
+
+    @property
+    def scm_instance(self):
+        import rhodecode
+        full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
+        if full_cache:
+            return self.scm_instance_cached()
+        return self.__get_instance()
+
+    def scm_instance_cached(self, valid_cache_keys=None):
+        @cache_region('long_term')
+        def _c(repo_name):
+            return self.__get_instance()
+        rn = self.repo_name
+
+        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
+        if not valid:
+            log.debug('Cache for %s invalidated, getting new object' % (rn))
+            region_invalidate(_c, None, rn)
+        else:
+            log.debug('Getting obj for %s from cache' % (rn))
+        return _c(rn)
+
+    def __get_instance(self):
+        repo_full_path = self.repo_full_path
+        try:
+            alias = get_scm(repo_full_path)[0]
+            log.debug('Creating instance of %s repository from %s'
+                      % (alias, repo_full_path))
+            backend = get_backend(alias)
+        except VCSError:
+            log.error(traceback.format_exc())
+            log.error('Perhaps this repository is in db and not in '
+                      'filesystem run rescan repositories with '
+                      '"destroy old data " option from admin panel')
+            return
+
+        if alias == 'hg':
+
+            repo = backend(safe_str(repo_full_path), create=False,
+                           baseui=self._ui)
+            # skip hidden web repository
+            if repo._get_hidden():
+                return
+        else:
+            repo = backend(repo_full_path, create=False)
+
+        return repo
+
+    def __json__(self):
+        return dict(landing_rev = self.landing_rev)
+
+class RepoGroup(Base, BaseModel):
+    __tablename__ = 'groups'
+    __table_args__ = (
+        UniqueConstraint('group_name', 'group_parent_id'),
+        CheckConstraint('group_id != group_parent_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    __mapper_args__ = {'order_by': 'group_name'}
+
+    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
+    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    parent_group = relationship('RepoGroup', remote_side=group_id)
+    user = relationship('User')
+
+    def __init__(self, group_name='', parent_group=None):
+        self.group_name = group_name
+        self.parent_group = parent_group
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
+                                      self.group_name)
+
+    @classmethod
+    def groups_choices(cls, groups=None, show_empty_group=True):
+        from webhelpers.html import literal as _literal
+        if not groups:
+            groups = cls.query().all()
+
+        repo_groups = []
+        if show_empty_group:
+            repo_groups = [('-1', u'-- %s --' % _('top level'))]
+        sep = ' &raquo; '
+        _name = lambda k: _literal(sep.join(k))
+
+        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
+                              for x in groups])
+
+        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
+        return repo_groups
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+        if case_insensitive:
+            gr = cls.query()\
+                .filter(cls.group_name.ilike(group_name))
+        else:
+            gr = cls.query()\
+                .filter(cls.group_name == group_name)
+        if cache:
+            gr = gr.options(FromCache(
+                            "sql_cache_short",
+                            "get_group_%s" % _hash_key(group_name)
+                            )
+            )
+        return gr.scalar()
+
+    @property
+    def parents(self):
+        parents_recursion_limit = 5
+        groups = []
+        if self.parent_group is None:
+            return groups
+        cur_gr = self.parent_group
+        groups.insert(0, cur_gr)
+        cnt = 0
+        while 1:
+            cnt += 1
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            if cnt == parents_recursion_limit:
+                # this will prevent accidental infinit loops
+                log.error('group nested more than %s' %
+                          parents_recursion_limit)
+                break
+
+            groups.insert(0, gr)
+        return groups
+
+    @property
+    def children(self):
+        return RepoGroup.query().filter(RepoGroup.parent_group == self)
+
+    @property
+    def name(self):
+        return self.group_name.split(RepoGroup.url_sep())[-1]
+
+    @property
+    def full_path(self):
+        return self.group_name
+
+    @property
+    def full_path_splitted(self):
+        return self.group_name.split(RepoGroup.url_sep())
+
+    @property
+    def repositories(self):
+        return Repository.query()\
+                .filter(Repository.group == self)\
+                .order_by(Repository.repo_name)
+
+    @property
+    def repositories_recursive_count(self):
+        cnt = self.repositories.count()
+
+        def children_count(group):
+            cnt = 0
+            for child in group.children:
+                cnt += child.repositories.count()
+                cnt += children_count(child)
+            return cnt
+
+        return cnt + children_count(self)
+
+    def _recursive_objects(self, include_repos=True):
+        all_ = []
+
+        def _get_members(root_gr):
+            if include_repos:
+                for r in root_gr.repositories:
+                    all_.append(r)
+            childs = root_gr.children.all()
+            if childs:
+                for gr in childs:
+                    all_.append(gr)
+                    _get_members(gr)
+
+        _get_members(self)
+        return [self] + all_
+
+    def recursive_groups_and_repos(self):
+        """
+        Recursive return all groups, with repositories in those groups
+        """
+        return self._recursive_objects()
+
+    def recursive_groups(self):
+        """
+        Returns all children groups for this group including children of children
+        """
+        return self._recursive_objects(include_repos=False)
+
+    def get_new_name(self, group_name):
+        """
+        returns new full group name based on parent and new name
+
+        :param group_name:
+        """
+        path_prefix = (self.parent_group.full_path_splitted if
+                       self.parent_group else [])
+        return RepoGroup.url_sep().join(path_prefix + [group_name])
+
+    def get_api_data(self):
+        """
+        Common function for generating api data
+
+        """
+        group = self
+        data = dict(
+            group_id=group.group_id,
+            group_name=group.group_name,
+            group_description=group.group_description,
+            parent_group=group.parent_group.group_name if group.parent_group else None,
+            repositories=[x.repo_name for x in group.repositories],
+            owner=group.user.username
+        )
+        return data
+
+
+class Permission(Base, BaseModel):
+    __tablename__ = 'permissions'
+    __table_args__ = (
+        Index('p_perm_name_idx', 'permission_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PERMS = [
+        ('hg.admin', _('RhodeCode Administrator')),
+
+        ('repository.none', _('Repository no access')),
+        ('repository.read', _('Repository read access')),
+        ('repository.write', _('Repository write access')),
+        ('repository.admin', _('Repository admin access')),
+
+        ('group.none', _('Repository group no access')),
+        ('group.read', _('Repository group read access')),
+        ('group.write', _('Repository group write access')),
+        ('group.admin', _('Repository group admin access')),
+
+        ('usergroup.none', _('User group no access')),
+        ('usergroup.read', _('User group read access')),
+        ('usergroup.write', _('User group write access')),
+        ('usergroup.admin', _('User group admin access')),
+
+        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
+        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
+
+        ('hg.usergroup.create.false', _('User Group creation disabled')),
+        ('hg.usergroup.create.true', _('User Group creation enabled')),
+
+        ('hg.create.none', _('Repository creation disabled')),
+        ('hg.create.repository', _('Repository creation enabled')),
+
+        ('hg.fork.none', _('Repository forking disabled')),
+        ('hg.fork.repository', _('Repository forking enabled')),
+
+        ('hg.register.none', _('Registration disabled')),
+        ('hg.register.manual_activate', _('User Registration with manual account activation')),
+        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
+
+        ('hg.extern_activate.manual', _('Manual activation of external account')),
+        ('hg.extern_activate.auto', _('Automatic activation of external account')),
+
+    ]
+
+    #definition of system default permissions for DEFAULT user
+    DEFAULT_USER_PERMISSIONS = [
+        'repository.read',
+        'group.read',
+        'usergroup.read',
+        'hg.create.repository',
+        'hg.fork.repository',
+        'hg.register.manual_activate',
+        'hg.extern_activate.auto',
+    ]
+
+    # defines which permissions are more important higher the more important
+    # Weight defines which permissions are more important.
+    # The higher number the more important.
+    PERM_WEIGHTS = {
+        'repository.none': 0,
+        'repository.read': 1,
+        'repository.write': 3,
+        'repository.admin': 4,
+
+        'group.none': 0,
+        'group.read': 1,
+        'group.write': 3,
+        'group.admin': 4,
+
+        'usergroup.none': 0,
+        'usergroup.read': 1,
+        'usergroup.write': 3,
+        'usergroup.admin': 4,
+        'hg.repogroup.create.false': 0,
+        'hg.repogroup.create.true': 1,
+
+        'hg.usergroup.create.false': 0,
+        'hg.usergroup.create.true': 1,
+
+        'hg.fork.none': 0,
+        'hg.fork.repository': 1,
+        'hg.create.none': 0,
+        'hg.create.repository': 1
+    }
+
+    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__, self.permission_id, self.permission_name
+        )
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.permission_name == key).scalar()
+
+    @classmethod
+    def get_default_perms(cls, default_user_id):
+        q = Session().query(UserRepoToPerm, Repository, cls)\
+         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
+         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_group_perms(cls, default_user_id):
+        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
+         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
+         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_user_group_perms(cls, default_user_id):
+        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
+         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
+         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserUserGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+
+class UserRepoToPerm(Base, BaseModel):
+    __tablename__ = 'repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    repository = relationship('Repository')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, repository, permission):
+        n = cls()
+        n.user = user
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.repository)
+
+
+class UserUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    user_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, user_group, permission):
+        n = cls()
+        n.user = user
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.user_group)
+
+
+class UserToPerm(Base, BaseModel):
+    __tablename__ = 'user_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    permission = relationship('Permission', lazy='joined')
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.permission)
+
+
+class UserGroupRepoToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    repository = relationship('Repository')
+
+    @classmethod
+    def create(cls, users_group, repository, permission):
+        n = cls()
+        n.users_group = users_group
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
+
+
+class UserGroupUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_group_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
+        CheckConstraint('target_user_group_id != user_group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
+    user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, target_user_group, user_group, permission):
+        n = cls()
+        n.target_user_group = target_user_group
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
+
+
+class UserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'permission_id',),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+
+class UserRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    group = relationship('RepoGroup')
+    permission = relationship('Permission')
+
+
+class UserGroupRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    group = relationship('RepoGroup')
+
+
+class Statistics(Base, BaseModel):
+    __tablename__ = 'statistics'
+    __table_args__ = (
+         UniqueConstraint('repository_id'),
+         {'extend_existing': True, 'mysql_engine': 'InnoDB',
+          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
+    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
+    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
+    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
+
+    repository = relationship('Repository', single_parent=True)
+
+
+class UserFollowing(Base, BaseModel):
+    __tablename__ = 'user_followings'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'follows_repository_id'),
+        UniqueConstraint('user_id', 'follows_user_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
+    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+
+    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
+
+    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
+    follows_repository = relationship('Repository', order_by='Repository.repo_name')
+
+    @classmethod
+    def get_repo_followers(cls, repo_id):
+        return cls.query().filter(cls.follows_repo_id == repo_id)
+
+
+class CacheInvalidation(Base, BaseModel):
+    __tablename__ = 'cache_invalidation'
+    __table_args__ = (
+        UniqueConstraint('cache_key'),
+        Index('key_idx', 'cache_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    # cache_id, not used
+    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    # cache_key as created by _get_cache_key
+    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    # cache_args is a repo_name
+    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    # instance sets cache_active True when it is caching,
+    # other instances set cache_active to False to indicate that this cache is invalid
+    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+
+    def __init__(self, cache_key, repo_name=''):
+        self.cache_key = cache_key
+        self.cache_args = repo_name
+        self.cache_active = False
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
+                            self.cache_id, self.cache_key, self.cache_active)
+
+    def _cache_key_partition(self):
+        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
+        return prefix, repo_name, suffix
+
+    def get_prefix(self):
+        """
+        get prefix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[0]
+
+    def get_suffix(self):
+        """
+        get suffix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[2]
+
+    @classmethod
+    def clear_cache(cls):
+        """
+        Delete all cache keys from database.
+        Should only be run when all instances are down and all entries thus stale.
+        """
+        cls.query().delete()
+        Session().commit()
+
+    @classmethod
+    def _get_cache_key(cls, key):
+        """
+        Wrapper for generating a unique cache key for this instance and "key".
+        key must / will start with a repo_name which will be stored in .cache_args .
+        """
+        import rhodecode
+        prefix = rhodecode.CONFIG.get('instance_id', '')
+        return "%s%s" % (prefix, key)
+
+    @classmethod
+    def set_invalidate(cls, repo_name, delete=False):
+        """
+        Mark all caches of a repo as invalid in the database.
+        """
+        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
+
+        try:
+            for inv_obj in inv_objs:
+                log.debug('marking %s key for invalidation based on repo_name=%s'
+                          % (inv_obj, safe_str(repo_name)))
+                if delete:
+                    Session().delete(inv_obj)
+                else:
+                    inv_obj.cache_active = False
+                    Session().add(inv_obj)
+            Session().commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+
+    @classmethod
+    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
+        """
+        Mark this cache key as active and currently cached.
+        Return True if the existing cache registration still was valid.
+        Return False to indicate that it had been invalidated and caches should be refreshed.
+        """
+
+        key = (repo_name + '_' + kind) if kind else repo_name
+        cache_key = cls._get_cache_key(key)
+
+        if valid_cache_keys and cache_key in valid_cache_keys:
+            return True
+
+        try:
+            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
+            if not inv_obj:
+                inv_obj = CacheInvalidation(cache_key, repo_name)
+            was_valid = inv_obj.cache_active
+            inv_obj.cache_active = True
+            Session().add(inv_obj)
+            Session().commit()
+            return was_valid
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+            return False
+
+    @classmethod
+    def get_valid_cache_keys(cls):
+        """
+        Return opaque object with information of which caches still are valid
+        and can be used without checking for invalidation.
+        """
+        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
+
+
+class ChangesetComment(Base, BaseModel):
+    __tablename__ = 'changeset_comments'
+    __table_args__ = (
+        Index('cc_revision_idx', 'revision'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    revision = Column('revision', String(40), nullable=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+    line_no = Column('line_no', Unicode(10), nullable=True)
+    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
+    f_path = Column('f_path', Unicode(1000), nullable=True)
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+    text = Column('text', UnicodeText(25000), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    @classmethod
+    def get_users(cls, revision=None, pull_request_id=None):
+        """
+        Returns user associated with this ChangesetComment. ie those
+        who actually commented
+
+        :param cls:
+        :param revision:
+        """
+        q = Session().query(User)\
+                .join(ChangesetComment.author)
+        if revision:
+            q = q.filter(cls.revision == revision)
+        elif pull_request_id:
+            q = q.filter(cls.pull_request_id == pull_request_id)
+        return q.all()
+
+
+class ChangesetStatus(Base, BaseModel):
+    __tablename__ = 'changeset_statuses'
+    __table_args__ = (
+        Index('cs_revision_idx', 'revision'),
+        Index('cs_version_idx', 'version'),
+        UniqueConstraint('repo_id', 'revision', 'version'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
+    STATUS_APPROVED = 'approved'
+    STATUS_REJECTED = 'rejected'
+    STATUS_UNDER_REVIEW = 'under_review'
+
+    STATUSES = [
+        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
+        (STATUS_APPROVED, _("Approved")),
+        (STATUS_REJECTED, _("Rejected")),
+        (STATUS_UNDER_REVIEW, _("Under Review")),
+    ]
+
+    changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    revision = Column('revision', String(40), nullable=False)
+    status = Column('status', String(128), nullable=False, default=DEFAULT)
+    changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
+    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
+    version = Column('version', Integer(), nullable=False, default=0)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    comment = relationship('ChangesetComment', lazy='joined')
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__,
+            self.status, self.author
+        )
+
+    @classmethod
+    def get_status_lbl(cls, value):
+        return dict(cls.STATUSES).get(value)
+
+    @property
+    def status_lbl(self):
+        return ChangesetStatus.get_status_lbl(self.status)
+
+
+class PullRequest(Base, BaseModel):
+    __tablename__ = 'pull_requests'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    # values for .status
+    STATUS_NEW = u'new'
+    STATUS_OPEN = u'open'
+    STATUS_CLOSED = u'closed'
+
+    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
+    title = Column('title', Unicode(256), nullable=True)
+    description = Column('description', UnicodeText(10240), nullable=True)
+    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    _revisions = Column('revisions', UnicodeText(20500))  # 500 revisions max
+    org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    org_ref = Column('org_ref', Unicode(256), nullable=False)
+    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    other_ref = Column('other_ref', Unicode(256), nullable=False)
+
+    @hybrid_property
+    def revisions(self):
+        return self._revisions.split(':')
+
+    @revisions.setter
+    def revisions(self, val):
+        self._revisions = ':'.join(val)
+
+    @property
+    def org_ref_parts(self):
+        return self.org_ref.split(':')
+
+    @property
+    def other_ref_parts(self):
+        return self.other_ref.split(':')
+
+    author = relationship('User', lazy='joined')
+    reviewers = relationship('PullRequestReviewers',
+                             cascade="all, delete, delete-orphan")
+    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
+    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
+    statuses = relationship('ChangesetStatus')
+    comments = relationship('ChangesetComment',
+                             cascade="all, delete, delete-orphan")
+
+    def is_closed(self):
+        return self.status == self.STATUS_CLOSED
+
+    @property
+    def last_review_status(self):
+        return self.statuses[-1].status if self.statuses else ''
+
+    def __json__(self):
+        return dict(
+            revisions=self.revisions
+        )
+
+
+class PullRequestReviewers(Base, BaseModel):
+    __tablename__ = 'pull_request_reviewers'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    def __init__(self, user=None, pull_request=None):
+        self.user = user
+        self.pull_request = pull_request
+
+    pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
+
+    user = relationship('User')
+    pull_request = relationship('PullRequest')
+
+
+class Notification(Base, BaseModel):
+    __tablename__ = 'notifications'
+    __table_args__ = (
+        Index('notification_type_idx', 'type'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    TYPE_CHANGESET_COMMENT = u'cs_comment'
+    TYPE_MESSAGE = u'message'
+    TYPE_MENTION = u'mention'
+    TYPE_REGISTRATION = u'registration'
+    TYPE_PULL_REQUEST = u'pull_request'
+    TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
+
+    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
+    subject = Column('subject', Unicode(512), nullable=True)
+    body = Column('body', UnicodeText(50000), nullable=True)
+    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    type_ = Column('type', Unicode(256))
+
+    created_by_user = relationship('User')
+    notifications_to_users = relationship('UserNotification', lazy='joined',
+                                          cascade="all, delete, delete-orphan")
+
+    @property
+    def recipients(self):
+        return [x.user for x in UserNotification.query()\
+                .filter(UserNotification.notification == self)\
+                .order_by(UserNotification.user_id.asc()).all()]
+
+    @classmethod
+    def create(cls, created_by, subject, body, recipients, type_=None):
+        if type_ is None:
+            type_ = Notification.TYPE_MESSAGE
+
+        notification = cls()
+        notification.created_by_user = created_by
+        notification.subject = subject
+        notification.body = body
+        notification.type_ = type_
+        notification.created_on = datetime.datetime.now()
+
+        for u in recipients:
+            assoc = UserNotification()
+            assoc.notification = notification
+            u.notifications.append(assoc)
+        Session().add(notification)
+        return notification
+
+    @property
+    def description(self):
+        from rhodecode.model.notification import NotificationModel
+        return NotificationModel().make_description(self)
+
+
+class UserNotification(Base, BaseModel):
+    __tablename__ = 'user_to_notification'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'notification_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
+    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
+    read = Column('read', Boolean, default=False)
+    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
+
+    user = relationship('User', lazy="joined")
+    notification = relationship('Notification', lazy="joined",
+                                order_by=lambda: Notification.created_on.desc(),)
+
+    def mark_as_read(self):
+        self.read = True
+        Session().add(self)
+
+
+class Gist(Base, BaseModel):
+    __tablename__ = 'gists'
+    __table_args__ = (
+        Index('g_gist_access_id_idx', 'gist_access_id'),
+        Index('g_created_on_idx', 'created_on'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    GIST_PUBLIC = u'public'
+    GIST_PRIVATE = u'private'
+
+    gist_id = Column('gist_id', Integer(), primary_key=True)
+    gist_access_id = Column('gist_access_id', Unicode(250))
+    gist_description = Column('gist_description', UnicodeText(1024))
+    gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
+    gist_expires = Column('gist_expires', Float(53), nullable=False)
+    gist_type = Column('gist_type', Unicode(128), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    owner = relationship('User')
+
+    @classmethod
+    def get_or_404(cls, id_):
+        res = cls.query().filter(cls.gist_access_id == id_).scalar()
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def get_by_access_id(cls, gist_access_id):
+        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
+
+    def gist_url(self):
+        import rhodecode
+        alias_url = rhodecode.CONFIG.get('gist_alias_url')
+        if alias_url:
+            return alias_url.replace('{gistid}', self.gist_access_id)
+
+        from pylons import url
+        return url('gist', gist_id=self.gist_access_id, qualified=True)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all gists are stored
+
+        :param cls:
+        """
+        from rhodecode.model.gist import GIST_STORE_LOC
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == URL_SEP)
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
+
+    def get_api_data(self):
+        """
+        Common function for generating gist related data for API
+        """
+        gist = self
+        data = dict(
+            gist_id=gist.gist_id,
+            type=gist.gist_type,
+            access_id=gist.gist_access_id,
+            description=gist.gist_description,
+            url=gist.gist_url(),
+            expires=gist.gist_expires,
+            created_on=gist.created_on,
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+        )
+        data.update(self.get_api_data())
+        return data
+    ## SCM functions
+
+    @property
+    def scm_instance(self):
+        from rhodecode.lib.vcs import get_repo
+        base_path = self.base_path()
+        return get_repo(os.path.join(*map(safe_str,
+                                          [base_path, self.gist_access_id])))
+
+
+class DbMigrateVersion(Base, BaseModel):
+    __tablename__ = 'db_migrate_version'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    repository_id = Column('repository_id', String(250), primary_key=True)
+    repository_path = Column('repository_path', Text)
+    version = Column('version', Integer)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/schema/db_2_1_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,2391 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db
+~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import os
+import time
+import logging
+import datetime
+import traceback
+import hashlib
+import collections
+import functools
+
+from sqlalchemy import *
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
+from sqlalchemy.exc import DatabaseError
+from beaker.cache import cache_region, region_invalidate
+from webob.exc import HTTPNotFound
+
+from pylons.i18n.translation import lazy_ugettext as _
+
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
+
+from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
+    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int
+from rhodecode.lib.compat import json
+from rhodecode.lib.caching_query import FromCache
+
+from rhodecode.model.meta import Base, Session
+
+URL_SEP = '/'
+log = logging.getLogger(__name__)
+
+#==============================================================================
+# BASE CLASSES
+#==============================================================================
+
+_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
+
+
+class BaseModel(object):
+    """
+    Base Model for all classess
+    """
+
+    @classmethod
+    def _get_keys(cls):
+        """return column names for this model """
+        return class_mapper(cls).c.keys()
+
+    def get_dict(self):
+        """
+        return dict with keys and values corresponding
+        to this model data """
+
+        d = {}
+        for k in self._get_keys():
+            d[k] = getattr(self, k)
+
+        # also use __json__() if present to get additional fields
+        _json_attr = getattr(self, '__json__', None)
+        if _json_attr:
+            # update with attributes from __json__
+            if callable(_json_attr):
+                _json_attr = _json_attr()
+            for k, val in _json_attr.iteritems():
+                d[k] = val
+        return d
+
+    def get_appstruct(self):
+        """return list with keys and values tupples corresponding
+        to this model data """
+
+        l = []
+        for k in self._get_keys():
+            l.append((k, getattr(self, k),))
+        return l
+
+    def populate_obj(self, populate_dict):
+        """populate model with data from given populate_dict"""
+
+        for k in self._get_keys():
+            if k in populate_dict:
+                setattr(self, k, populate_dict[k])
+
+    @classmethod
+    def query(cls):
+        return Session().query(cls)
+
+    @classmethod
+    def get(cls, id_):
+        if id_:
+            return cls.query().get(id_)
+
+    @classmethod
+    def get_or_404(cls, id_):
+        try:
+            id_ = int(id_)
+        except (TypeError, ValueError):
+            raise HTTPNotFound
+
+        res = cls.query().get(id_)
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def getAll(cls):
+        # deprecated and left for backward compatibility
+        return cls.get_all()
+
+    @classmethod
+    def get_all(cls):
+        return cls.query().all()
+
+    @classmethod
+    def delete(cls, id_):
+        obj = cls.query().get(id_)
+        Session().delete(obj)
+
+    def __repr__(self):
+        if hasattr(self, '__unicode__'):
+            # python repr needs to return str
+            try:
+                return safe_str(self.__unicode__())
+            except UnicodeDecodeError:
+                pass
+        return '<DB:%s>' % (self.__class__.__name__)
+
+
+class RhodeCodeSetting(Base, BaseModel):
+    __tablename__ = 'rhodecode_settings'
+    __table_args__ = (
+        UniqueConstraint('app_settings_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    SETTINGS_TYPES = {
+        'str': safe_str,
+        'int': safe_int,
+        'unicode': safe_unicode,
+        'bool': str2bool,
+        'list': functools.partial(aslist, sep=',')
+    }
+    DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
+
+    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __init__(self, key='', val='', type='unicode'):
+        self.app_settings_name = key
+        self.app_settings_value = val
+        self.app_settings_type = type
+
+    @validates('_app_settings_value')
+    def validate_settings_value(self, key, val):
+        assert type(val) == unicode
+        return val
+
+    @hybrid_property
+    def app_settings_value(self):
+        v = self._app_settings_value
+        _type = self.app_settings_type
+        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
+        return converter(v)
+
+    @app_settings_value.setter
+    def app_settings_value(self, val):
+        """
+        Setter that will always make sure we use unicode in app_settings_value
+
+        :param val:
+        """
+        self._app_settings_value = safe_unicode(val)
+
+    @hybrid_property
+    def app_settings_type(self):
+        return self._app_settings_type
+
+    @app_settings_type.setter
+    def app_settings_type(self, val):
+        if val not in self.SETTINGS_TYPES:
+            raise Exception('type must be one of %s got %s'
+                            % (self.SETTINGS_TYPES.keys(), val))
+        self._app_settings_type = val
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (
+            self.__class__.__name__,
+            self.app_settings_name, self.app_settings_value, self.app_settings_type
+        )
+
+    @classmethod
+    def get_by_name(cls, key):
+        return cls.query()\
+            .filter(cls.app_settings_name == key).scalar()
+
+    @classmethod
+    def get_by_name_or_create(cls, key, val='', type='unicode'):
+        res = cls.get_by_name(key)
+        if not res:
+            res = cls(key, val, type)
+        return res
+
+    @classmethod
+    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
+        """
+        Creates or updates RhodeCode setting. If updates is triggered it will only
+        update parameters that are explicityl set Optional instance will be skipped
+
+        :param key:
+        :param val:
+        :param type:
+        :return:
+        """
+        res = cls.get_by_name(key)
+        if not res:
+            val = Optional.extract(val)
+            type = Optional.extract(type)
+            res = cls(key, val, type)
+        else:
+            res.app_settings_name = key
+            if not isinstance(val, Optional):
+                # update if set
+                res.app_settings_value = val
+            if not isinstance(type, Optional):
+                # update if set
+                res.app_settings_type = type
+        return res
+
+    @classmethod
+    def get_app_settings(cls, cache=False):
+
+        ret = cls.query()
+
+        if cache:
+            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
+
+        if not ret:
+            raise Exception('Could not get application settings !')
+        settings = {}
+        for each in ret:
+            settings['rhodecode_' + each.app_settings_name] = \
+                each.app_settings_value
+
+        return settings
+
+    @classmethod
+    def get_auth_plugins(cls, cache=False):
+        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
+        return auth_plugins
+
+    @classmethod
+    def get_auth_settings(cls, cache=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('auth_')).all()
+        fd = {}
+        for row in ret:
+            fd.update({row.app_settings_name: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('default_')).all()
+        fd = {}
+        for row in ret:
+            key = row.app_settings_name
+            if strip_prefix:
+                key = remove_prefix(key, prefix='default_')
+            fd.update({key: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_server_info(cls):
+        import pkg_resources
+        import platform
+        import rhodecode
+        from rhodecode.lib.utils import check_git_version
+        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
+        info = {
+            'modules': sorted(mods, key=lambda k: k[0].lower()),
+            'py_version': platform.python_version(),
+            'platform': safe_unicode(platform.platform()),
+            'rhodecode_version': rhodecode.__version__,
+            'git_version': safe_unicode(check_git_version()),
+            'git_path': rhodecode.CONFIG.get('git_path')
+        }
+        return info
+
+
+class RhodeCodeUi(Base, BaseModel):
+    __tablename__ = 'rhodecode_ui'
+    __table_args__ = (
+        UniqueConstraint('ui_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    HOOK_UPDATE = 'changegroup.update'
+    HOOK_REPO_SIZE = 'changegroup.repo_size'
+    HOOK_PUSH = 'changegroup.push_logger'
+    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
+    HOOK_PULL = 'outgoing.pull_logger'
+    HOOK_PRE_PULL = 'preoutgoing.pre_pull'
+
+    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
+
+    # def __init__(self, section='', key='', value=''):
+    #     self.ui_section = section
+    #     self.ui_key = key
+    #     self.ui_value = value
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.ui_key == key).scalar()
+
+    @classmethod
+    def get_builtin_hooks(cls):
+        q = cls.query()
+        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        return q.all()
+
+    @classmethod
+    def get_custom_hooks(cls):
+        q = cls.query()
+        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        q = q.filter(cls.ui_section == 'hooks')
+        return q.all()
+
+    @classmethod
+    def get_repos_location(cls):
+        return cls.get_by_key('/').ui_value
+
+    @classmethod
+    def create_or_update_hook(cls, key, val):
+        new_ui = cls.get_by_key(key) or cls()
+        new_ui.ui_section = 'hooks'
+        new_ui.ui_active = True
+        new_ui.ui_key = key
+        new_ui.ui_value = val
+
+        Session().add(new_ui)
+
+    def __repr__(self):
+        return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
+                                    self.ui_key, self.ui_value)
+
+
+class User(Base, BaseModel):
+    __tablename__ = 'users'
+    __table_args__ = (
+        UniqueConstraint('username'), UniqueConstraint('email'),
+        Index('u_username_idx', 'username'),
+        Index('u_email_idx', 'email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    DEFAULT_USER = 'default'
+    DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
+
+    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
+    extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    user_log = relationship('UserLog')
+    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
+
+    repositories = relationship('Repository')
+    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
+    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
+
+    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
+
+    group_member = relationship('UserGroupMember', cascade='all')
+
+    notifications = relationship('UserNotification', cascade='all')
+    # notifications assigned to this user
+    user_created_notifications = relationship('Notification', cascade='all')
+    # comments created by this user
+    user_comments = relationship('ChangesetComment', cascade='all')
+    #extra emails for this user
+    user_emails = relationship('UserEmailMap', cascade='all')
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+    @property
+    def firstname(self):
+        # alias for future
+        return self.name
+
+    @property
+    def emails(self):
+        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
+        return [self.email] + [x.email for x in other]
+
+    @property
+    def ip_addresses(self):
+        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
+        return [x.ip_addr for x in ret]
+
+    @property
+    def username_and_name(self):
+        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
+
+    @property
+    def full_name(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def full_name_or_username(self):
+        return ('%s %s' % (self.firstname, self.lastname)
+                if (self.firstname and self.lastname) else self.username)
+
+    @property
+    def full_contact(self):
+        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
+
+    @property
+    def short_contact(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def is_admin(self):
+        return self.admin
+
+    @property
+    def AuthUser(self):
+        """
+        Returns instance of AuthUser for this user
+        """
+        from rhodecode.lib.auth import AuthUser
+        return AuthUser(user_id=self.user_id, api_key=self.api_key,
+                        username=self.username)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.user_id, self.username)
+
+    @classmethod
+    def get_by_username(cls, username, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.username.ilike(username))
+        else:
+            q = cls.query().filter(cls.username == username)
+
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(username)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get_by_api_key(cls, api_key, cache=False, fallback=True):
+        q = cls.query().filter(cls.api_key == api_key)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_api_key_%s" % api_key))
+        res = q.scalar()
+
+        if fallback and not res:
+            #fallback to additional keys
+            _res = UserApiKeys.query()\
+                .filter(UserApiKeys.api_key == api_key)\
+                .filter(or_(UserApiKeys.expires == -1,
+                            UserApiKeys.expires >= time.time()))\
+                .first()
+            if _res:
+                res = _res.user
+        return res
+
+    @classmethod
+    def get_by_email(cls, email, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.email.ilike(email))
+        else:
+            q = cls.query().filter(cls.email == email)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_email_key_%s" % email))
+
+        ret = q.scalar()
+        if ret is None:
+            q = UserEmailMap.query()
+            # try fetching in alternate email map
+            if case_insensitive:
+                q = q.filter(UserEmailMap.email.ilike(email))
+            else:
+                q = q.filter(UserEmailMap.email == email)
+            q = q.options(joinedload(UserEmailMap.user))
+            if cache:
+                q = q.options(FromCache("sql_cache_short",
+                                        "get_email_map_key_%s" % email))
+            ret = getattr(q.scalar(), 'user', None)
+
+        return ret
+
+    @classmethod
+    def get_from_cs_author(cls, author):
+        """
+        Tries to get User objects out of commit author string
+
+        :param author:
+        """
+        from rhodecode.lib.helpers import email, author_name
+        # Valid email in the attribute passed, see if they're in the system
+        _email = email(author)
+        if _email:
+            user = cls.get_by_email(_email, case_insensitive=True)
+            if user:
+                return user
+        # Maybe we can match by username?
+        _author = author_name(author)
+        user = cls.get_by_username(_author, case_insensitive=True)
+        if user:
+            return user
+
+    def update_lastlogin(self):
+        """Update user lastlogin"""
+        self.last_login = datetime.datetime.now()
+        Session().add(self)
+        log.debug('updated user %s lastlogin' % self.username)
+
+    @classmethod
+    def get_first_admin(cls):
+        user = User.query().filter(User.admin == True).first()
+        if user is None:
+            raise Exception('Missing administrative account!')
+        return user
+
+    @classmethod
+    def get_default_user(cls, cache=False):
+        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
+        if user is None:
+            raise Exception('Missing default account!')
+        return user
+
+    def get_api_data(self):
+        """
+        Common function for generating user related data for API
+        """
+        user = self
+        data = dict(
+            user_id=user.user_id,
+            username=user.username,
+            firstname=user.name,
+            lastname=user.lastname,
+            email=user.email,
+            emails=user.emails,
+            api_key=user.api_key,
+            active=user.active,
+            admin=user.admin,
+            extern_type=user.extern_type,
+            extern_name=user.extern_name,
+            last_login=user.last_login,
+            ip_addresses=user.ip_addresses
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+            full_name=self.full_name,
+            full_name_or_username=self.full_name_or_username,
+            short_contact=self.short_contact,
+            full_contact=self.full_contact
+        )
+        data.update(self.get_api_data())
+        return data
+
+
+class UserApiKeys(Base, BaseModel):
+    __tablename__ = 'user_api_keys'
+    __table_args__ = (
+        Index('uak_api_key_idx', 'api_key'),
+        UniqueConstraint('api_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True)
+    description = Column('description', UnicodeText(1024))
+    expires = Column('expires', Float(53), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    user = relationship('User', lazy='joined')
+
+
+class UserEmailMap(Base, BaseModel):
+    __tablename__ = 'user_email_map'
+    __table_args__ = (
+        Index('uem_email_idx', 'email'),
+        UniqueConstraint('email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    user = relationship('User', lazy='joined')
+
+    @validates('_email')
+    def validate_email(self, key, email):
+        # check if this email is not main one
+        main_email = Session().query(User).filter(User.email == email).scalar()
+        if main_email is not None:
+            raise AttributeError('email %s is present is user table' % email)
+        return email
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+
+class UserIpMap(Base, BaseModel):
+    __tablename__ = 'user_ip_map'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'ip_addr'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    user = relationship('User', lazy='joined')
+
+    @classmethod
+    def _get_ip_range(cls, ip_addr):
+        from rhodecode.lib import ipaddr
+        net = ipaddr.IPNetwork(address=ip_addr)
+        return [str(net.network), str(net.broadcast)]
+
+    def __json__(self):
+        return dict(
+          ip_addr=self.ip_addr,
+          ip_range=self._get_ip_range(self.ip_addr)
+        )
+
+    def __unicode__(self):
+        return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
+                                            self.user_id, self.ip_addr)
+
+class UserLog(Base, BaseModel):
+    __tablename__ = 'user_logs'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.repository_name,
+                                      self.action)
+
+    @property
+    def action_as_day(self):
+        return datetime.date(*self.action_date.timetuple()[:3])
+
+    user = relationship('User')
+    repository = relationship('Repository', cascade='')
+
+
+class UserGroup(Base, BaseModel):
+    __tablename__ = 'users_groups'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
+    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
+    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
+    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
+
+    user = relationship('User')
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.users_group_id,
+                                      self.users_group_name)
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False,
+                          case_insensitive=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.users_group_name.ilike(group_name))
+        else:
+            q = cls.query().filter(cls.users_group_name == group_name)
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(group_name)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get(cls, user_group_id, cache=False):
+        user_group = cls.query()
+        if cache:
+            user_group = user_group.options(FromCache("sql_cache_short",
+                                    "get_users_group_%s" % user_group_id))
+        return user_group.get(user_group_id)
+
+    def get_api_data(self, with_members=True):
+        user_group = self
+
+        data = dict(
+            users_group_id=user_group.users_group_id,
+            group_name=user_group.users_group_name,
+            group_description=user_group.user_group_description,
+            active=user_group.users_group_active,
+            owner=user_group.user.username,
+        )
+        if with_members:
+            members = []
+            for user in user_group.members:
+                user = user.user
+                members.append(user.get_api_data())
+            data['members'] = members
+
+        return data
+
+
+class UserGroupMember(Base, BaseModel):
+    __tablename__ = 'users_groups_members'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User', lazy='joined')
+    users_group = relationship('UserGroup')
+
+    def __init__(self, gr_id='', u_id=''):
+        self.users_group_id = gr_id
+        self.user_id = u_id
+
+
+class RepositoryField(Base, BaseModel):
+    __tablename__ = 'repositories_fields'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PREFIX = 'ex_'  # prefix used in form to not conflict with already existing fields
+
+    repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
+    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_type = Column("field_type", String(256), nullable=False, unique=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repository = relationship('Repository')
+
+    @property
+    def field_key_prefixed(self):
+        return 'ex_%s' % self.field_key
+
+    @classmethod
+    def un_prefix_key(cls, key):
+        if key.startswith(cls.PREFIX):
+            return key[len(cls.PREFIX):]
+        return key
+
+    @classmethod
+    def get_by_key_name(cls, key, repo):
+        row = cls.query()\
+                .filter(cls.repository == repo)\
+                .filter(cls.field_key == key).scalar()
+        return row
+
+
+class Repository(Base, BaseModel):
+    __tablename__ = 'repositories'
+    __table_args__ = (
+        UniqueConstraint('repo_name'),
+        Index('r_repo_name_idx', 'repo_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
+
+    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
+    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
+    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
+    description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    _landing_revision = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
+
+    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
+
+    user = relationship('User')
+    fork = relationship('Repository', remote_side=repo_id)
+    group = relationship('RepoGroup')
+    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    stats = relationship('Statistics', cascade='all', uselist=False)
+
+    followers = relationship('UserFollowing',
+                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
+                             cascade='all')
+    extra_fields = relationship('RepositoryField',
+                                cascade="all, delete, delete-orphan")
+
+    logs = relationship('UserLog')
+    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
+
+    pull_requests_org = relationship('PullRequest',
+                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    pull_requests_other = relationship('PullRequest',
+                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
+                                   safe_unicode(self.repo_name))
+
+    @hybrid_property
+    def landing_rev(self):
+        # always should return [rev_type, rev]
+        if self._landing_revision:
+            _rev_info = self._landing_revision.split(':')
+            if len(_rev_info) < 2:
+                _rev_info.insert(0, 'rev')
+            return [_rev_info[0], _rev_info[1]]
+        return [None, None]
+
+    @landing_rev.setter
+    def landing_rev(self, val):
+        if ':' not in val:
+            raise ValueError('value must be delimited with `:` and consist '
+                             'of <rev_type>:<rev>, got %s instead' % val)
+        self._landing_revision = val
+
+    @hybrid_property
+    def locked(self):
+        # always should return [user_id, timelocked]
+        if self._locked:
+            _lock_info = self._locked.split(':')
+            return int(_lock_info[0]), _lock_info[1]
+        return [None, None]
+
+    @locked.setter
+    def locked(self, val):
+        if val and isinstance(val, (list, tuple)):
+            self._locked = ':'.join(map(str, val))
+        else:
+            self._locked = None
+
+    @hybrid_property
+    def changeset_cache(self):
+        from rhodecode.lib.vcs.backends.base import EmptyChangeset
+        dummy = EmptyChangeset().__json__()
+        if not self._changeset_cache:
+            return dummy
+        try:
+            return json.loads(self._changeset_cache)
+        except TypeError:
+            return dummy
+
+    @changeset_cache.setter
+    def changeset_cache(self, val):
+        try:
+            self._changeset_cache = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def normalize_repo_name(cls, repo_name):
+        """
+        Normalizes os specific repo_name to the format internally stored inside
+        dabatabase using URL_SEP
+
+        :param cls:
+        :param repo_name:
+        """
+        return cls.url_sep().join(repo_name.split(os.sep))
+
+    @classmethod
+    def get_by_repo_name(cls, repo_name):
+        q = Session().query(cls).filter(cls.repo_name == repo_name)
+        q = q.options(joinedload(Repository.fork))\
+                .options(joinedload(Repository.user))\
+                .options(joinedload(Repository.group))
+        return q.scalar()
+
+    @classmethod
+    def get_by_full_path(cls, repo_full_path):
+        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
+        repo_name = cls.normalize_repo_name(repo_name)
+        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
+
+    @classmethod
+    def get_repo_forks(cls, repo_id):
+        return cls.query().filter(Repository.fork_id == repo_id)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all repos are stored
+
+        :param cls:
+        """
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == cls.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def forks(self):
+        """
+        Return forks of this repo
+        """
+        return Repository.get_repo_forks(self.repo_id)
+
+    @property
+    def parent(self):
+        """
+        Returns fork parent
+        """
+        return self.fork
+
+    @property
+    def just_name(self):
+        return self.repo_name.split(Repository.url_sep())[-1]
+
+    @property
+    def groups_with_parents(self):
+        groups = []
+        if self.group is None:
+            return groups
+
+        cur_gr = self.group
+        groups.insert(0, cur_gr)
+        while 1:
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            groups.insert(0, gr)
+
+        return groups
+
+    @property
+    def groups_and_repo(self):
+        return self.groups_with_parents, self.just_name, self.repo_name
+
+    @LazyProperty
+    def repo_path(self):
+        """
+        Returns base full path for that repository means where it actually
+        exists on a filesystem
+        """
+        q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+                                              Repository.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def repo_full_path(self):
+        p = [self.repo_path]
+        # we need to split the name by / since this is how we store the
+        # names in the database, but that eventually needs to be converted
+        # into a valid system path
+        p += self.repo_name.split(Repository.url_sep())
+        return os.path.join(*map(safe_unicode, p))
+
+    @property
+    def cache_keys(self):
+        """
+        Returns associated cache keys for that repo
+        """
+        return CacheInvalidation.query()\
+            .filter(CacheInvalidation.cache_args == self.repo_name)\
+            .order_by(CacheInvalidation.cache_key)\
+            .all()
+
+    def get_new_name(self, repo_name):
+        """
+        returns new full repository name based on assigned group and new new
+
+        :param group_name:
+        """
+        path_prefix = self.group.full_path_splitted if self.group else []
+        return Repository.url_sep().join(path_prefix + [repo_name])
+
+    @property
+    def _ui(self):
+        """
+        Creates an db based ui object for this repository
+        """
+        from rhodecode.lib.utils import make_ui
+        return make_ui('db', clear_session=False)
+
+    @classmethod
+    def is_valid(cls, repo_name):
+        """
+        returns True if given repo name is a valid filesystem repository
+
+        :param cls:
+        :param repo_name:
+        """
+        from rhodecode.lib.utils import is_valid_repo
+
+        return is_valid_repo(repo_name, cls.base_path())
+
+    def get_api_data(self):
+        """
+        Common function for generating repo api data
+
+        """
+        repo = self
+        data = dict(
+            repo_id=repo.repo_id,
+            repo_name=repo.repo_name,
+            repo_type=repo.repo_type,
+            clone_uri=repo.clone_uri,
+            private=repo.private,
+            created_on=repo.created_on,
+            description=repo.description,
+            landing_rev=repo.landing_rev,
+            owner=repo.user.username,
+            fork_of=repo.fork.repo_name if repo.fork else None,
+            enable_statistics=repo.enable_statistics,
+            enable_locking=repo.enable_locking,
+            enable_downloads=repo.enable_downloads,
+            last_changeset=repo.changeset_cache,
+            locked_by=User.get(self.locked[0]).get_api_data() \
+                if self.locked[0] else None,
+            locked_date=time_to_datetime(self.locked[1]) \
+                if self.locked[1] else None
+        )
+        rc_config = RhodeCodeSetting.get_app_settings()
+        repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
+        if repository_fields:
+            for f in self.extra_fields:
+                data[f.field_key_prefixed] = f.field_value
+
+        return data
+
+    @classmethod
+    def lock(cls, repo, user_id, lock_time=None):
+        if not lock_time:
+            lock_time = time.time()
+        repo.locked = [user_id, lock_time]
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def unlock(cls, repo):
+        repo.locked = None
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def getlock(cls, repo):
+        return repo.locked
+
+    @property
+    def last_db_change(self):
+        return self.updated_on
+
+    def clone_url(self, **override):
+        from pylons import url
+        from urlparse import urlparse
+        import urllib
+        parsed_url = urlparse(url('home', qualified=True))
+        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
+        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
+        args = {
+           'user': '',
+           'pass': '',
+           'scheme': parsed_url.scheme,
+           'netloc': parsed_url.netloc,
+           'prefix': decoded_path,
+           'path': self.repo_name
+        }
+
+        args.update(override)
+        return default_clone_uri % args
+
+    #==========================================================================
+    # SCM PROPERTIES
+    #==========================================================================
+
+    def get_changeset(self, rev=None):
+        return get_changeset_safe(self.scm_instance, rev)
+
+    def get_landing_changeset(self):
+        """
+        Returns landing changeset, or if that doesn't exist returns the tip
+        """
+        _rev_type, _rev = self.landing_rev
+        cs = self.get_changeset(_rev)
+        if isinstance(cs, EmptyChangeset):
+            return self.get_changeset()
+        return cs
+
+    def update_changeset_cache(self, cs_cache=None):
+        """
+        Update cache of last changeset for repository, keys should be::
+
+            short_id
+            raw_id
+            revision
+            message
+            date
+            author
+
+        :param cs_cache:
+        """
+        from rhodecode.lib.vcs.backends.base import BaseChangeset
+        if cs_cache is None:
+            cs_cache = EmptyChangeset()
+            # use no-cache version here
+            scm_repo = self.scm_instance_no_cache()
+            if scm_repo:
+                cs_cache = scm_repo.get_changeset()
+
+        if isinstance(cs_cache, BaseChangeset):
+            cs_cache = cs_cache.__json__()
+
+        if (cs_cache != self.changeset_cache or not self.changeset_cache):
+            _default = datetime.datetime.fromtimestamp(0)
+            last_change = cs_cache.get('date') or _default
+            log.debug('updated repo %s with new cs cache %s'
+                      % (self.repo_name, cs_cache))
+            self.updated_on = last_change
+            self.changeset_cache = cs_cache
+            Session().add(self)
+            Session().commit()
+        else:
+            log.debug('Skipping repo:%s already with latest changes'
+                      % self.repo_name)
+
+    @property
+    def tip(self):
+        return self.get_changeset('tip')
+
+    @property
+    def author(self):
+        return self.tip.author
+
+    @property
+    def last_change(self):
+        return self.scm_instance.last_change
+
+    def get_comments(self, revisions=None):
+        """
+        Returns comments for this repository grouped by revisions
+
+        :param revisions: filter query by revisions only
+        """
+        cmts = ChangesetComment.query()\
+            .filter(ChangesetComment.repo == self)
+        if revisions:
+            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
+        grouped = collections.defaultdict(list)
+        for cmt in cmts.all():
+            grouped[cmt.revision].append(cmt)
+        return grouped
+
+    def statuses(self, revisions=None):
+        """
+        Returns statuses for this repository
+
+        :param revisions: list of revisions to get statuses for
+        """
+
+        statuses = ChangesetStatus.query()\
+            .filter(ChangesetStatus.repo == self)\
+            .filter(ChangesetStatus.version == 0)
+        if revisions:
+            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
+        grouped = {}
+
+        #maybe we have open new pullrequest without a status ?
+        stat = ChangesetStatus.STATUS_UNDER_REVIEW
+        status_lbl = ChangesetStatus.get_status_lbl(stat)
+        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
+            for rev in pr.revisions:
+                pr_id = pr.pull_request_id
+                pr_repo = pr.other_repo.repo_name
+                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
+
+        for stat in statuses.all():
+            pr_id = pr_repo = None
+            if stat.pull_request:
+                pr_id = stat.pull_request.pull_request_id
+                pr_repo = stat.pull_request.other_repo.repo_name
+            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
+                                      pr_id, pr_repo]
+        return grouped
+
+    def _repo_size(self):
+        from rhodecode.lib import helpers as h
+        log.debug('calculating repository size...')
+        return h.format_byte_size(self.scm_instance.size)
+
+    #==========================================================================
+    # SCM CACHE INSTANCE
+    #==========================================================================
+
+    def set_invalidate(self):
+        """
+        Mark caches of this repo as invalid.
+        """
+        CacheInvalidation.set_invalidate(self.repo_name)
+
+    def scm_instance_no_cache(self):
+        return self.__get_instance()
+
+    @property
+    def scm_instance(self):
+        import rhodecode
+        full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
+        if full_cache:
+            return self.scm_instance_cached()
+        return self.__get_instance()
+
+    def scm_instance_cached(self, valid_cache_keys=None):
+        @cache_region('long_term')
+        def _c(repo_name):
+            return self.__get_instance()
+        rn = self.repo_name
+
+        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
+        if not valid:
+            log.debug('Cache for %s invalidated, getting new object' % (rn))
+            region_invalidate(_c, None, rn)
+        else:
+            log.debug('Getting obj for %s from cache' % (rn))
+        return _c(rn)
+
+    def __get_instance(self):
+        repo_full_path = self.repo_full_path
+        try:
+            alias = get_scm(repo_full_path)[0]
+            log.debug('Creating instance of %s repository from %s'
+                      % (alias, repo_full_path))
+            backend = get_backend(alias)
+        except VCSError:
+            log.error(traceback.format_exc())
+            log.error('Perhaps this repository is in db and not in '
+                      'filesystem run rescan repositories with '
+                      '"destroy old data " option from admin panel')
+            return
+
+        if alias == 'hg':
+
+            repo = backend(safe_str(repo_full_path), create=False,
+                           baseui=self._ui)
+            # skip hidden web repository
+            if repo._get_hidden():
+                return
+        else:
+            repo = backend(repo_full_path, create=False)
+
+        return repo
+
+    def __json__(self):
+        return dict(landing_rev = self.landing_rev)
+
+class RepoGroup(Base, BaseModel):
+    __tablename__ = 'groups'
+    __table_args__ = (
+        UniqueConstraint('group_name', 'group_parent_id'),
+        CheckConstraint('group_id != group_parent_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    __mapper_args__ = {'order_by': 'group_name'}
+
+    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
+    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    parent_group = relationship('RepoGroup', remote_side=group_id)
+    user = relationship('User')
+
+    def __init__(self, group_name='', parent_group=None):
+        self.group_name = group_name
+        self.parent_group = parent_group
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
+                                      self.group_name)
+
+    @classmethod
+    def groups_choices(cls, groups=None, show_empty_group=True):
+        from webhelpers.html import literal as _literal
+        if not groups:
+            groups = cls.query().all()
+
+        repo_groups = []
+        if show_empty_group:
+            repo_groups = [('-1', u'-- %s --' % _('top level'))]
+        sep = ' &raquo; '
+        _name = lambda k: _literal(sep.join(k))
+
+        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
+                              for x in groups])
+
+        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
+        return repo_groups
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+        if case_insensitive:
+            gr = cls.query()\
+                .filter(cls.group_name.ilike(group_name))
+        else:
+            gr = cls.query()\
+                .filter(cls.group_name == group_name)
+        if cache:
+            gr = gr.options(FromCache(
+                            "sql_cache_short",
+                            "get_group_%s" % _hash_key(group_name)
+                            )
+            )
+        return gr.scalar()
+
+    @property
+    def parents(self):
+        parents_recursion_limit = 5
+        groups = []
+        if self.parent_group is None:
+            return groups
+        cur_gr = self.parent_group
+        groups.insert(0, cur_gr)
+        cnt = 0
+        while 1:
+            cnt += 1
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            if cnt == parents_recursion_limit:
+                # this will prevent accidental infinit loops
+                log.error('group nested more than %s' %
+                          parents_recursion_limit)
+                break
+
+            groups.insert(0, gr)
+        return groups
+
+    @property
+    def children(self):
+        return RepoGroup.query().filter(RepoGroup.parent_group == self)
+
+    @property
+    def name(self):
+        return self.group_name.split(RepoGroup.url_sep())[-1]
+
+    @property
+    def full_path(self):
+        return self.group_name
+
+    @property
+    def full_path_splitted(self):
+        return self.group_name.split(RepoGroup.url_sep())
+
+    @property
+    def repositories(self):
+        return Repository.query()\
+                .filter(Repository.group == self)\
+                .order_by(Repository.repo_name)
+
+    @property
+    def repositories_recursive_count(self):
+        cnt = self.repositories.count()
+
+        def children_count(group):
+            cnt = 0
+            for child in group.children:
+                cnt += child.repositories.count()
+                cnt += children_count(child)
+            return cnt
+
+        return cnt + children_count(self)
+
+    def _recursive_objects(self, include_repos=True):
+        all_ = []
+
+        def _get_members(root_gr):
+            if include_repos:
+                for r in root_gr.repositories:
+                    all_.append(r)
+            childs = root_gr.children.all()
+            if childs:
+                for gr in childs:
+                    all_.append(gr)
+                    _get_members(gr)
+
+        _get_members(self)
+        return [self] + all_
+
+    def recursive_groups_and_repos(self):
+        """
+        Recursive return all groups, with repositories in those groups
+        """
+        return self._recursive_objects()
+
+    def recursive_groups(self):
+        """
+        Returns all children groups for this group including children of children
+        """
+        return self._recursive_objects(include_repos=False)
+
+    def get_new_name(self, group_name):
+        """
+        returns new full group name based on parent and new name
+
+        :param group_name:
+        """
+        path_prefix = (self.parent_group.full_path_splitted if
+                       self.parent_group else [])
+        return RepoGroup.url_sep().join(path_prefix + [group_name])
+
+    def get_api_data(self):
+        """
+        Common function for generating api data
+
+        """
+        group = self
+        data = dict(
+            group_id=group.group_id,
+            group_name=group.group_name,
+            group_description=group.group_description,
+            parent_group=group.parent_group.group_name if group.parent_group else None,
+            repositories=[x.repo_name for x in group.repositories],
+            owner=group.user.username
+        )
+        return data
+
+
+class Permission(Base, BaseModel):
+    __tablename__ = 'permissions'
+    __table_args__ = (
+        Index('p_perm_name_idx', 'permission_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PERMS = [
+        ('hg.admin', _('RhodeCode Administrator')),
+
+        ('repository.none', _('Repository no access')),
+        ('repository.read', _('Repository read access')),
+        ('repository.write', _('Repository write access')),
+        ('repository.admin', _('Repository admin access')),
+
+        ('group.none', _('Repository group no access')),
+        ('group.read', _('Repository group read access')),
+        ('group.write', _('Repository group write access')),
+        ('group.admin', _('Repository group admin access')),
+
+        ('usergroup.none', _('User group no access')),
+        ('usergroup.read', _('User group read access')),
+        ('usergroup.write', _('User group write access')),
+        ('usergroup.admin', _('User group admin access')),
+
+        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
+        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
+
+        ('hg.usergroup.create.false', _('User Group creation disabled')),
+        ('hg.usergroup.create.true', _('User Group creation enabled')),
+
+        ('hg.create.none', _('Repository creation disabled')),
+        ('hg.create.repository', _('Repository creation enabled')),
+
+        ('hg.fork.none', _('Repository forking disabled')),
+        ('hg.fork.repository', _('Repository forking enabled')),
+
+        ('hg.register.none', _('Registration disabled')),
+        ('hg.register.manual_activate', _('User Registration with manual account activation')),
+        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
+
+        ('hg.extern_activate.manual', _('Manual activation of external account')),
+        ('hg.extern_activate.auto', _('Automatic activation of external account')),
+
+    ]
+
+    #definition of system default permissions for DEFAULT user
+    DEFAULT_USER_PERMISSIONS = [
+        'repository.read',
+        'group.read',
+        'usergroup.read',
+        'hg.create.repository',
+        'hg.fork.repository',
+        'hg.register.manual_activate',
+        'hg.extern_activate.auto',
+    ]
+
+    # defines which permissions are more important higher the more important
+    # Weight defines which permissions are more important.
+    # The higher number the more important.
+    PERM_WEIGHTS = {
+        'repository.none': 0,
+        'repository.read': 1,
+        'repository.write': 3,
+        'repository.admin': 4,
+
+        'group.none': 0,
+        'group.read': 1,
+        'group.write': 3,
+        'group.admin': 4,
+
+        'usergroup.none': 0,
+        'usergroup.read': 1,
+        'usergroup.write': 3,
+        'usergroup.admin': 4,
+        'hg.repogroup.create.false': 0,
+        'hg.repogroup.create.true': 1,
+
+        'hg.usergroup.create.false': 0,
+        'hg.usergroup.create.true': 1,
+
+        'hg.fork.none': 0,
+        'hg.fork.repository': 1,
+        'hg.create.none': 0,
+        'hg.create.repository': 1
+    }
+
+    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__, self.permission_id, self.permission_name
+        )
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.permission_name == key).scalar()
+
+    @classmethod
+    def get_default_perms(cls, default_user_id):
+        q = Session().query(UserRepoToPerm, Repository, cls)\
+         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
+         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_group_perms(cls, default_user_id):
+        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
+         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
+         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_user_group_perms(cls, default_user_id):
+        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
+         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
+         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserUserGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+
+class UserRepoToPerm(Base, BaseModel):
+    __tablename__ = 'repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    repository = relationship('Repository')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, repository, permission):
+        n = cls()
+        n.user = user
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.repository)
+
+
+class UserUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    user_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, user_group, permission):
+        n = cls()
+        n.user = user
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.user_group)
+
+
+class UserToPerm(Base, BaseModel):
+    __tablename__ = 'user_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    permission = relationship('Permission', lazy='joined')
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.permission)
+
+
+class UserGroupRepoToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    repository = relationship('Repository')
+
+    @classmethod
+    def create(cls, users_group, repository, permission):
+        n = cls()
+        n.users_group = users_group
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
+
+
+class UserGroupUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_group_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
+        CheckConstraint('target_user_group_id != user_group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
+    user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, target_user_group, user_group, permission):
+        n = cls()
+        n.target_user_group = target_user_group
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
+
+
+class UserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'permission_id',),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+
+class UserRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    group = relationship('RepoGroup')
+    permission = relationship('Permission')
+
+
+class UserGroupRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    group = relationship('RepoGroup')
+
+
+class Statistics(Base, BaseModel):
+    __tablename__ = 'statistics'
+    __table_args__ = (
+         UniqueConstraint('repository_id'),
+         {'extend_existing': True, 'mysql_engine': 'InnoDB',
+          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
+    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
+    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
+    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
+
+    repository = relationship('Repository', single_parent=True)
+
+
+class UserFollowing(Base, BaseModel):
+    __tablename__ = 'user_followings'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'follows_repository_id'),
+        UniqueConstraint('user_id', 'follows_user_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
+    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+
+    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
+
+    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
+    follows_repository = relationship('Repository', order_by='Repository.repo_name')
+
+    @classmethod
+    def get_repo_followers(cls, repo_id):
+        return cls.query().filter(cls.follows_repo_id == repo_id)
+
+
+class CacheInvalidation(Base, BaseModel):
+    __tablename__ = 'cache_invalidation'
+    __table_args__ = (
+        UniqueConstraint('cache_key'),
+        Index('key_idx', 'cache_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    # cache_id, not used
+    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    # cache_key as created by _get_cache_key
+    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    # cache_args is a repo_name
+    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    # instance sets cache_active True when it is caching,
+    # other instances set cache_active to False to indicate that this cache is invalid
+    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+
+    def __init__(self, cache_key, repo_name=''):
+        self.cache_key = cache_key
+        self.cache_args = repo_name
+        self.cache_active = False
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
+                            self.cache_id, self.cache_key, self.cache_active)
+
+    def _cache_key_partition(self):
+        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
+        return prefix, repo_name, suffix
+
+    def get_prefix(self):
+        """
+        get prefix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[0]
+
+    def get_suffix(self):
+        """
+        get suffix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[2]
+
+    @classmethod
+    def clear_cache(cls):
+        """
+        Delete all cache keys from database.
+        Should only be run when all instances are down and all entries thus stale.
+        """
+        cls.query().delete()
+        Session().commit()
+
+    @classmethod
+    def _get_cache_key(cls, key):
+        """
+        Wrapper for generating a unique cache key for this instance and "key".
+        key must / will start with a repo_name which will be stored in .cache_args .
+        """
+        import rhodecode
+        prefix = rhodecode.CONFIG.get('instance_id', '')
+        return "%s%s" % (prefix, key)
+
+    @classmethod
+    def set_invalidate(cls, repo_name, delete=False):
+        """
+        Mark all caches of a repo as invalid in the database.
+        """
+        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
+
+        try:
+            for inv_obj in inv_objs:
+                log.debug('marking %s key for invalidation based on repo_name=%s'
+                          % (inv_obj, safe_str(repo_name)))
+                if delete:
+                    Session().delete(inv_obj)
+                else:
+                    inv_obj.cache_active = False
+                    Session().add(inv_obj)
+            Session().commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+
+    @classmethod
+    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
+        """
+        Mark this cache key as active and currently cached.
+        Return True if the existing cache registration still was valid.
+        Return False to indicate that it had been invalidated and caches should be refreshed.
+        """
+
+        key = (repo_name + '_' + kind) if kind else repo_name
+        cache_key = cls._get_cache_key(key)
+
+        if valid_cache_keys and cache_key in valid_cache_keys:
+            return True
+
+        try:
+            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
+            if not inv_obj:
+                inv_obj = CacheInvalidation(cache_key, repo_name)
+            was_valid = inv_obj.cache_active
+            inv_obj.cache_active = True
+            Session().add(inv_obj)
+            Session().commit()
+            return was_valid
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+            return False
+
+    @classmethod
+    def get_valid_cache_keys(cls):
+        """
+        Return opaque object with information of which caches still are valid
+        and can be used without checking for invalidation.
+        """
+        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
+
+
+class ChangesetComment(Base, BaseModel):
+    __tablename__ = 'changeset_comments'
+    __table_args__ = (
+        Index('cc_revision_idx', 'revision'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    revision = Column('revision', String(40), nullable=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+    line_no = Column('line_no', Unicode(10), nullable=True)
+    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
+    f_path = Column('f_path', Unicode(1000), nullable=True)
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+    text = Column('text', UnicodeText(25000), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    @classmethod
+    def get_users(cls, revision=None, pull_request_id=None):
+        """
+        Returns user associated with this ChangesetComment. ie those
+        who actually commented
+
+        :param cls:
+        :param revision:
+        """
+        q = Session().query(User)\
+                .join(ChangesetComment.author)
+        if revision:
+            q = q.filter(cls.revision == revision)
+        elif pull_request_id:
+            q = q.filter(cls.pull_request_id == pull_request_id)
+        return q.all()
+
+
+class ChangesetStatus(Base, BaseModel):
+    __tablename__ = 'changeset_statuses'
+    __table_args__ = (
+        Index('cs_revision_idx', 'revision'),
+        Index('cs_version_idx', 'version'),
+        UniqueConstraint('repo_id', 'revision', 'version'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
+    STATUS_APPROVED = 'approved'
+    STATUS_REJECTED = 'rejected'
+    STATUS_UNDER_REVIEW = 'under_review'
+
+    STATUSES = [
+        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
+        (STATUS_APPROVED, _("Approved")),
+        (STATUS_REJECTED, _("Rejected")),
+        (STATUS_UNDER_REVIEW, _("Under Review")),
+    ]
+
+    changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    revision = Column('revision', String(40), nullable=False)
+    status = Column('status', String(128), nullable=False, default=DEFAULT)
+    changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
+    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
+    version = Column('version', Integer(), nullable=False, default=0)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    comment = relationship('ChangesetComment', lazy='joined')
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__,
+            self.status, self.author
+        )
+
+    @classmethod
+    def get_status_lbl(cls, value):
+        return dict(cls.STATUSES).get(value)
+
+    @property
+    def status_lbl(self):
+        return ChangesetStatus.get_status_lbl(self.status)
+
+
+class PullRequest(Base, BaseModel):
+    __tablename__ = 'pull_requests'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    # values for .status
+    STATUS_NEW = u'new'
+    STATUS_OPEN = u'open'
+    STATUS_CLOSED = u'closed'
+
+    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
+    title = Column('title', Unicode(256), nullable=True)
+    description = Column('description', UnicodeText(10240), nullable=True)
+    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    _revisions = Column('revisions', UnicodeText(20500))  # 500 revisions max
+    org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    org_ref = Column('org_ref', Unicode(256), nullable=False)
+    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    other_ref = Column('other_ref', Unicode(256), nullable=False)
+
+    @hybrid_property
+    def revisions(self):
+        return self._revisions.split(':')
+
+    @revisions.setter
+    def revisions(self, val):
+        self._revisions = ':'.join(val)
+
+    @property
+    def org_ref_parts(self):
+        return self.org_ref.split(':')
+
+    @property
+    def other_ref_parts(self):
+        return self.other_ref.split(':')
+
+    author = relationship('User', lazy='joined')
+    reviewers = relationship('PullRequestReviewers',
+                             cascade="all, delete, delete-orphan")
+    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
+    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
+    statuses = relationship('ChangesetStatus')
+    comments = relationship('ChangesetComment',
+                             cascade="all, delete, delete-orphan")
+
+    def is_closed(self):
+        return self.status == self.STATUS_CLOSED
+
+    @property
+    def last_review_status(self):
+        return self.statuses[-1].status if self.statuses else ''
+
+    def __json__(self):
+        return dict(
+            revisions=self.revisions
+        )
+
+
+class PullRequestReviewers(Base, BaseModel):
+    __tablename__ = 'pull_request_reviewers'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    def __init__(self, user=None, pull_request=None):
+        self.user = user
+        self.pull_request = pull_request
+
+    pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
+
+    user = relationship('User')
+    pull_request = relationship('PullRequest')
+
+
+class Notification(Base, BaseModel):
+    __tablename__ = 'notifications'
+    __table_args__ = (
+        Index('notification_type_idx', 'type'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    TYPE_CHANGESET_COMMENT = u'cs_comment'
+    TYPE_MESSAGE = u'message'
+    TYPE_MENTION = u'mention'
+    TYPE_REGISTRATION = u'registration'
+    TYPE_PULL_REQUEST = u'pull_request'
+    TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
+
+    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
+    subject = Column('subject', Unicode(512), nullable=True)
+    body = Column('body', UnicodeText(50000), nullable=True)
+    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    type_ = Column('type', Unicode(256))
+
+    created_by_user = relationship('User')
+    notifications_to_users = relationship('UserNotification', lazy='joined',
+                                          cascade="all, delete, delete-orphan")
+
+    @property
+    def recipients(self):
+        return [x.user for x in UserNotification.query()\
+                .filter(UserNotification.notification == self)\
+                .order_by(UserNotification.user_id.asc()).all()]
+
+    @classmethod
+    def create(cls, created_by, subject, body, recipients, type_=None):
+        if type_ is None:
+            type_ = Notification.TYPE_MESSAGE
+
+        notification = cls()
+        notification.created_by_user = created_by
+        notification.subject = subject
+        notification.body = body
+        notification.type_ = type_
+        notification.created_on = datetime.datetime.now()
+
+        for u in recipients:
+            assoc = UserNotification()
+            assoc.notification = notification
+            u.notifications.append(assoc)
+        Session().add(notification)
+        return notification
+
+    @property
+    def description(self):
+        from rhodecode.model.notification import NotificationModel
+        return NotificationModel().make_description(self)
+
+
+class UserNotification(Base, BaseModel):
+    __tablename__ = 'user_to_notification'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'notification_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
+    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
+    read = Column('read', Boolean, default=False)
+    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
+
+    user = relationship('User', lazy="joined")
+    notification = relationship('Notification', lazy="joined",
+                                order_by=lambda: Notification.created_on.desc(),)
+
+    def mark_as_read(self):
+        self.read = True
+        Session().add(self)
+
+
+class Gist(Base, BaseModel):
+    __tablename__ = 'gists'
+    __table_args__ = (
+        Index('g_gist_access_id_idx', 'gist_access_id'),
+        Index('g_created_on_idx', 'created_on'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    GIST_PUBLIC = u'public'
+    GIST_PRIVATE = u'private'
+
+    gist_id = Column('gist_id', Integer(), primary_key=True)
+    gist_access_id = Column('gist_access_id', Unicode(250))
+    gist_description = Column('gist_description', UnicodeText(1024))
+    gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
+    gist_expires = Column('gist_expires', Float(53), nullable=False)
+    gist_type = Column('gist_type', Unicode(128), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    owner = relationship('User')
+
+    @classmethod
+    def get_or_404(cls, id_):
+        res = cls.query().filter(cls.gist_access_id == id_).scalar()
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def get_by_access_id(cls, gist_access_id):
+        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
+
+    def gist_url(self):
+        import rhodecode
+        alias_url = rhodecode.CONFIG.get('gist_alias_url')
+        if alias_url:
+            return alias_url.replace('{gistid}', self.gist_access_id)
+
+        from pylons import url
+        return url('gist', gist_id=self.gist_access_id, qualified=True)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all gists are stored
+
+        :param cls:
+        """
+        from rhodecode.model.gist import GIST_STORE_LOC
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == URL_SEP)
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
+
+    def get_api_data(self):
+        """
+        Common function for generating gist related data for API
+        """
+        gist = self
+        data = dict(
+            gist_id=gist.gist_id,
+            type=gist.gist_type,
+            access_id=gist.gist_access_id,
+            description=gist.gist_description,
+            url=gist.gist_url(),
+            expires=gist.gist_expires,
+            created_on=gist.created_on,
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+        )
+        data.update(self.get_api_data())
+        return data
+    ## SCM functions
+
+    @property
+    def scm_instance(self):
+        from rhodecode.lib.vcs import get_repo
+        base_path = self.base_path()
+        return get_repo(os.path.join(*map(safe_str,
+                                          [base_path, self.gist_access_id])))
+
+
+class DbMigrateVersion(Base, BaseModel):
+    __tablename__ = 'db_migrate_version'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    repository_id = Column('repository_id', String(250), primary_key=True)
+    repository_path = Column('repository_path', Text)
+    version = Column('version', Integer)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/schema/db_2_2_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,2448 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db
+~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import os
+import time
+import logging
+import datetime
+import traceback
+import hashlib
+import collections
+import functools
+
+from sqlalchemy import *
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
+from sqlalchemy.exc import DatabaseError
+from beaker.cache import cache_region, region_invalidate
+from webob.exc import HTTPNotFound
+
+from pylons.i18n.translation import lazy_ugettext as _
+
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
+
+from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
+    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \
+    get_clone_url
+from rhodecode.lib.compat import json
+from rhodecode.lib.caching_query import FromCache
+
+from rhodecode.model.meta import Base, Session
+
+URL_SEP = '/'
+log = logging.getLogger(__name__)
+
+#==============================================================================
+# BASE CLASSES
+#==============================================================================
+
+_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
+
+
+class BaseModel(object):
+    """
+    Base Model for all classess
+    """
+
+    @classmethod
+    def _get_keys(cls):
+        """return column names for this model """
+        return class_mapper(cls).c.keys()
+
+    def get_dict(self):
+        """
+        return dict with keys and values corresponding
+        to this model data """
+
+        d = {}
+        for k in self._get_keys():
+            d[k] = getattr(self, k)
+
+        # also use __json__() if present to get additional fields
+        _json_attr = getattr(self, '__json__', None)
+        if _json_attr:
+            # update with attributes from __json__
+            if callable(_json_attr):
+                _json_attr = _json_attr()
+            for k, val in _json_attr.iteritems():
+                d[k] = val
+        return d
+
+    def get_appstruct(self):
+        """return list with keys and values tupples corresponding
+        to this model data """
+
+        l = []
+        for k in self._get_keys():
+            l.append((k, getattr(self, k),))
+        return l
+
+    def populate_obj(self, populate_dict):
+        """populate model with data from given populate_dict"""
+
+        for k in self._get_keys():
+            if k in populate_dict:
+                setattr(self, k, populate_dict[k])
+
+    @classmethod
+    def query(cls):
+        return Session().query(cls)
+
+    @classmethod
+    def get(cls, id_):
+        if id_:
+            return cls.query().get(id_)
+
+    @classmethod
+    def get_or_404(cls, id_):
+        try:
+            id_ = int(id_)
+        except (TypeError, ValueError):
+            raise HTTPNotFound
+
+        res = cls.query().get(id_)
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def getAll(cls):
+        # deprecated and left for backward compatibility
+        return cls.get_all()
+
+    @classmethod
+    def get_all(cls):
+        return cls.query().all()
+
+    @classmethod
+    def delete(cls, id_):
+        obj = cls.query().get(id_)
+        Session().delete(obj)
+
+    def __repr__(self):
+        if hasattr(self, '__unicode__'):
+            # python repr needs to return str
+            try:
+                return safe_str(self.__unicode__())
+            except UnicodeDecodeError:
+                pass
+        return '<DB:%s>' % (self.__class__.__name__)
+
+
+class RhodeCodeSetting(Base, BaseModel):
+    __tablename__ = 'rhodecode_settings'
+    __table_args__ = (
+        UniqueConstraint('app_settings_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    SETTINGS_TYPES = {
+        'str': safe_str,
+        'int': safe_int,
+        'unicode': safe_unicode,
+        'bool': str2bool,
+        'list': functools.partial(aslist, sep=',')
+    }
+    DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
+
+    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None)
+    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+
+    def __init__(self, key='', val='', type='unicode'):
+        self.app_settings_name = key
+        self.app_settings_value = val
+        self.app_settings_type = type
+
+    @validates('_app_settings_value')
+    def validate_settings_value(self, key, val):
+        assert type(val) == unicode
+        return val
+
+    @hybrid_property
+    def app_settings_value(self):
+        v = self._app_settings_value
+        _type = self.app_settings_type
+        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
+        return converter(v)
+
+    @app_settings_value.setter
+    def app_settings_value(self, val):
+        """
+        Setter that will always make sure we use unicode in app_settings_value
+
+        :param val:
+        """
+        self._app_settings_value = safe_unicode(val)
+
+    @hybrid_property
+    def app_settings_type(self):
+        return self._app_settings_type
+
+    @app_settings_type.setter
+    def app_settings_type(self, val):
+        if val not in self.SETTINGS_TYPES:
+            raise Exception('type must be one of %s got %s'
+                            % (self.SETTINGS_TYPES.keys(), val))
+        self._app_settings_type = val
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (
+            self.__class__.__name__,
+            self.app_settings_name, self.app_settings_value, self.app_settings_type
+        )
+
+    @classmethod
+    def get_by_name(cls, key):
+        return cls.query()\
+            .filter(cls.app_settings_name == key).scalar()
+
+    @classmethod
+    def get_by_name_or_create(cls, key, val='', type='unicode'):
+        res = cls.get_by_name(key)
+        if not res:
+            res = cls(key, val, type)
+        return res
+
+    @classmethod
+    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
+        """
+        Creates or updates RhodeCode setting. If updates is triggered it will only
+        update parameters that are explicityl set Optional instance will be skipped
+
+        :param key:
+        :param val:
+        :param type:
+        :return:
+        """
+        res = cls.get_by_name(key)
+        if not res:
+            val = Optional.extract(val)
+            type = Optional.extract(type)
+            res = cls(key, val, type)
+        else:
+            res.app_settings_name = key
+            if not isinstance(val, Optional):
+                # update if set
+                res.app_settings_value = val
+            if not isinstance(type, Optional):
+                # update if set
+                res.app_settings_type = type
+        return res
+
+    @classmethod
+    def get_app_settings(cls, cache=False):
+
+        ret = cls.query()
+
+        if cache:
+            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
+
+        if not ret:
+            raise Exception('Could not get application settings !')
+        settings = {}
+        for each in ret:
+            settings['rhodecode_' + each.app_settings_name] = \
+                each.app_settings_value
+
+        return settings
+
+    @classmethod
+    def get_auth_plugins(cls, cache=False):
+        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
+        return auth_plugins
+
+    @classmethod
+    def get_auth_settings(cls, cache=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('auth_')).all()
+        fd = {}
+        for row in ret:
+            fd.update({row.app_settings_name: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('default_')).all()
+        fd = {}
+        for row in ret:
+            key = row.app_settings_name
+            if strip_prefix:
+                key = remove_prefix(key, prefix='default_')
+            fd.update({key: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_server_info(cls):
+        import pkg_resources
+        import platform
+        import rhodecode
+        from rhodecode.lib.utils import check_git_version
+        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
+        info = {
+            'modules': sorted(mods, key=lambda k: k[0].lower()),
+            'py_version': platform.python_version(),
+            'platform': safe_unicode(platform.platform()),
+            'rhodecode_version': rhodecode.__version__,
+            'git_version': safe_unicode(check_git_version()),
+            'git_path': rhodecode.CONFIG.get('git_path')
+        }
+        return info
+
+
+class RhodeCodeUi(Base, BaseModel):
+    __tablename__ = 'rhodecode_ui'
+    __table_args__ = (
+        UniqueConstraint('ui_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    HOOK_UPDATE = 'changegroup.update'
+    HOOK_REPO_SIZE = 'changegroup.repo_size'
+    HOOK_PUSH = 'changegroup.push_logger'
+    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
+    HOOK_PULL = 'outgoing.pull_logger'
+    HOOK_PRE_PULL = 'preoutgoing.pre_pull'
+
+    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
+
+    # def __init__(self, section='', key='', value=''):
+    #     self.ui_section = section
+    #     self.ui_key = key
+    #     self.ui_value = value
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.ui_key == key).scalar()
+
+    @classmethod
+    def get_builtin_hooks(cls):
+        q = cls.query()
+        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        return q.all()
+
+    @classmethod
+    def get_custom_hooks(cls):
+        q = cls.query()
+        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        q = q.filter(cls.ui_section == 'hooks')
+        return q.all()
+
+    @classmethod
+    def get_repos_location(cls):
+        return cls.get_by_key('/').ui_value
+
+    @classmethod
+    def create_or_update_hook(cls, key, val):
+        new_ui = cls.get_by_key(key) or cls()
+        new_ui.ui_section = 'hooks'
+        new_ui.ui_active = True
+        new_ui.ui_key = key
+        new_ui.ui_value = val
+
+        Session().add(new_ui)
+
+    def __repr__(self):
+        return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
+                                    self.ui_key, self.ui_value)
+
+
+class User(Base, BaseModel):
+    __tablename__ = 'users'
+    __table_args__ = (
+        UniqueConstraint('username'), UniqueConstraint('email'),
+        Index('u_username_idx', 'username'),
+        Index('u_email_idx', 'email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    DEFAULT_USER = 'default'
+    DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
+
+    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+    name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
+    extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    #_user_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data
+
+    user_log = relationship('UserLog')
+    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
+
+    repositories = relationship('Repository')
+    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
+    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
+
+    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
+
+    group_member = relationship('UserGroupMember', cascade='all')
+
+    notifications = relationship('UserNotification', cascade='all')
+    # notifications assigned to this user
+    user_created_notifications = relationship('Notification', cascade='all')
+    # comments created by this user
+    user_comments = relationship('ChangesetComment', cascade='all')
+    #extra emails for this user
+    user_emails = relationship('UserEmailMap', cascade='all')
+    #extra api keys
+    user_api_keys = relationship('UserApiKeys', cascade='all')
+
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+    @property
+    def firstname(self):
+        # alias for future
+        return self.name
+
+    @property
+    def emails(self):
+        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
+        return [self.email] + [x.email for x in other]
+
+    @property
+    def api_keys(self):
+        other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
+        return [self.api_key] + [x.api_key for x in other]
+
+    @property
+    def ip_addresses(self):
+        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
+        return [x.ip_addr for x in ret]
+
+    @property
+    def username_and_name(self):
+        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
+
+    @property
+    def full_name(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def full_name_or_username(self):
+        return ('%s %s' % (self.firstname, self.lastname)
+                if (self.firstname and self.lastname) else self.username)
+
+    @property
+    def full_contact(self):
+        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
+
+    @property
+    def short_contact(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def is_admin(self):
+        return self.admin
+
+    @property
+    def AuthUser(self):
+        """
+        Returns instance of AuthUser for this user
+        """
+        from rhodecode.lib.auth import AuthUser
+        return AuthUser(user_id=self.user_id, api_key=self.api_key,
+                        username=self.username)
+
+    @hybrid_property
+    def user_data(self):
+        if not self._user_data:
+            return {}
+
+        try:
+            return json.loads(self._user_data)
+        except TypeError:
+            return {}
+
+    @user_data.setter
+    def user_data(self, val):
+        try:
+            self._user_data = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.user_id, self.username)
+
+    @classmethod
+    def get_by_username(cls, username, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.username.ilike(username))
+        else:
+            q = cls.query().filter(cls.username == username)
+
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(username)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get_by_api_key(cls, api_key, cache=False, fallback=True):
+        q = cls.query().filter(cls.api_key == api_key)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_api_key_%s" % api_key))
+        res = q.scalar()
+
+        if fallback and not res:
+            #fallback to additional keys
+            _res = UserApiKeys.query()\
+                .filter(UserApiKeys.api_key == api_key)\
+                .filter(or_(UserApiKeys.expires == -1,
+                            UserApiKeys.expires >= time.time()))\
+                .first()
+            if _res:
+                res = _res.user
+        return res
+
+    @classmethod
+    def get_by_email(cls, email, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.email.ilike(email))
+        else:
+            q = cls.query().filter(cls.email == email)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_email_key_%s" % email))
+
+        ret = q.scalar()
+        if ret is None:
+            q = UserEmailMap.query()
+            # try fetching in alternate email map
+            if case_insensitive:
+                q = q.filter(UserEmailMap.email.ilike(email))
+            else:
+                q = q.filter(UserEmailMap.email == email)
+            q = q.options(joinedload(UserEmailMap.user))
+            if cache:
+                q = q.options(FromCache("sql_cache_short",
+                                        "get_email_map_key_%s" % email))
+            ret = getattr(q.scalar(), 'user', None)
+
+        return ret
+
+    @classmethod
+    def get_from_cs_author(cls, author):
+        """
+        Tries to get User objects out of commit author string
+
+        :param author:
+        """
+        from rhodecode.lib.helpers import email, author_name
+        # Valid email in the attribute passed, see if they're in the system
+        _email = email(author)
+        if _email:
+            user = cls.get_by_email(_email, case_insensitive=True)
+            if user:
+                return user
+        # Maybe we can match by username?
+        _author = author_name(author)
+        user = cls.get_by_username(_author, case_insensitive=True)
+        if user:
+            return user
+
+    def update_lastlogin(self):
+        """Update user lastlogin"""
+        self.last_login = datetime.datetime.now()
+        Session().add(self)
+        log.debug('updated user %s lastlogin' % self.username)
+
+    @classmethod
+    def get_first_admin(cls):
+        user = User.query().filter(User.admin == True).first()
+        if user is None:
+            raise Exception('Missing administrative account!')
+        return user
+
+    @classmethod
+    def get_default_user(cls, cache=False):
+        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
+        if user is None:
+            raise Exception('Missing default account!')
+        return user
+
+    def get_api_data(self):
+        """
+        Common function for generating user related data for API
+        """
+        user = self
+        data = dict(
+            user_id=user.user_id,
+            username=user.username,
+            firstname=user.name,
+            lastname=user.lastname,
+            email=user.email,
+            emails=user.emails,
+            api_key=user.api_key,
+            api_keys=user.api_keys,
+            active=user.active,
+            admin=user.admin,
+            extern_type=user.extern_type,
+            extern_name=user.extern_name,
+            last_login=user.last_login,
+            ip_addresses=user.ip_addresses
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+            full_name=self.full_name,
+            full_name_or_username=self.full_name_or_username,
+            short_contact=self.short_contact,
+            full_contact=self.full_contact
+        )
+        data.update(self.get_api_data())
+        return data
+
+
+class UserApiKeys(Base, BaseModel):
+    __tablename__ = 'user_api_keys'
+    __table_args__ = (
+        Index('uak_api_key_idx', 'api_key'),
+        Index('uak_api_key_expires_idx', 'api_key', 'expires'),
+        UniqueConstraint('api_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True)
+    description = Column('description', UnicodeText(1024))
+    expires = Column('expires', Float(53), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    user = relationship('User', lazy='joined')
+
+    @property
+    def expired(self):
+        if self.expires == -1:
+            return False
+        return time.time() > self.expires
+
+
+class UserEmailMap(Base, BaseModel):
+    __tablename__ = 'user_email_map'
+    __table_args__ = (
+        Index('uem_email_idx', 'email'),
+        UniqueConstraint('email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    user = relationship('User', lazy='joined')
+
+    @validates('_email')
+    def validate_email(self, key, email):
+        # check if this email is not main one
+        main_email = Session().query(User).filter(User.email == email).scalar()
+        if main_email is not None:
+            raise AttributeError('email %s is present is user table' % email)
+        return email
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+
+class UserIpMap(Base, BaseModel):
+    __tablename__ = 'user_ip_map'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'ip_addr'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    user = relationship('User', lazy='joined')
+
+    @classmethod
+    def _get_ip_range(cls, ip_addr):
+        from rhodecode.lib import ipaddr
+        net = ipaddr.IPNetwork(address=ip_addr)
+        return [str(net.network), str(net.broadcast)]
+
+    def __json__(self):
+        return dict(
+          ip_addr=self.ip_addr,
+          ip_range=self._get_ip_range(self.ip_addr)
+        )
+
+    def __unicode__(self):
+        return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
+                                            self.user_id, self.ip_addr)
+
+class UserLog(Base, BaseModel):
+    __tablename__ = 'user_logs'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None)
+    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.repository_name,
+                                      self.action)
+
+    @property
+    def action_as_day(self):
+        return datetime.date(*self.action_date.timetuple()[:3])
+
+    user = relationship('User')
+    repository = relationship('Repository', cascade='')
+
+
+class UserGroup(Base, BaseModel):
+    __tablename__ = 'users_groups'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
+    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
+    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
+    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
+
+    user = relationship('User')
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.users_group_id,
+                                      self.users_group_name)
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False,
+                          case_insensitive=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.users_group_name.ilike(group_name))
+        else:
+            q = cls.query().filter(cls.users_group_name == group_name)
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(group_name)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get(cls, user_group_id, cache=False):
+        user_group = cls.query()
+        if cache:
+            user_group = user_group.options(FromCache("sql_cache_short",
+                                    "get_users_group_%s" % user_group_id))
+        return user_group.get(user_group_id)
+
+    def get_api_data(self, with_members=True):
+        user_group = self
+
+        data = dict(
+            users_group_id=user_group.users_group_id,
+            group_name=user_group.users_group_name,
+            group_description=user_group.user_group_description,
+            active=user_group.users_group_active,
+            owner=user_group.user.username,
+        )
+        if with_members:
+            members = []
+            for user in user_group.members:
+                user = user.user
+                members.append(user.get_api_data())
+            data['members'] = members
+
+        return data
+
+
+class UserGroupMember(Base, BaseModel):
+    __tablename__ = 'users_groups_members'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User', lazy='joined')
+    users_group = relationship('UserGroup')
+
+    def __init__(self, gr_id='', u_id=''):
+        self.users_group_id = gr_id
+        self.user_id = u_id
+
+
+class RepositoryField(Base, BaseModel):
+    __tablename__ = 'repositories_fields'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PREFIX = 'ex_'  # prefix used in form to not conflict with already existing fields
+
+    repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    field_key = Column("field_key", String(250, convert_unicode=False))
+    field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False)
+    field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False)
+    field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False)
+    field_type = Column("field_type", String(256), nullable=False, unique=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repository = relationship('Repository')
+
+    @property
+    def field_key_prefixed(self):
+        return 'ex_%s' % self.field_key
+
+    @classmethod
+    def un_prefix_key(cls, key):
+        if key.startswith(cls.PREFIX):
+            return key[len(cls.PREFIX):]
+        return key
+
+    @classmethod
+    def get_by_key_name(cls, key, repo):
+        row = cls.query()\
+                .filter(cls.repository == repo)\
+                .filter(cls.field_key == key).scalar()
+        return row
+
+
+class Repository(Base, BaseModel):
+    __tablename__ = 'repositories'
+    __table_args__ = (
+        UniqueConstraint('repo_name'),
+        Index('r_repo_name_idx', 'repo_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
+    DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
+
+    STATE_CREATED = 'repo_state_created'
+    STATE_PENDING = 'repo_state_pending'
+    STATE_ERROR = 'repo_state_error'
+
+    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
+    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
+    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
+    description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    _landing_revision = Column("landing_revision", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    _locked = Column("locked", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
+
+    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
+
+    user = relationship('User')
+    fork = relationship('Repository', remote_side=repo_id)
+    group = relationship('RepoGroup')
+    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    stats = relationship('Statistics', cascade='all', uselist=False)
+
+    followers = relationship('UserFollowing',
+                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
+                             cascade='all')
+    extra_fields = relationship('RepositoryField',
+                                cascade="all, delete, delete-orphan")
+
+    logs = relationship('UserLog')
+    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
+
+    pull_requests_org = relationship('PullRequest',
+                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    pull_requests_other = relationship('PullRequest',
+                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
+                                   safe_unicode(self.repo_name))
+
+    @hybrid_property
+    def landing_rev(self):
+        # always should return [rev_type, rev]
+        if self._landing_revision:
+            _rev_info = self._landing_revision.split(':')
+            if len(_rev_info) < 2:
+                _rev_info.insert(0, 'rev')
+            return [_rev_info[0], _rev_info[1]]
+        return [None, None]
+
+    @landing_rev.setter
+    def landing_rev(self, val):
+        if ':' not in val:
+            raise ValueError('value must be delimited with `:` and consist '
+                             'of <rev_type>:<rev>, got %s instead' % val)
+        self._landing_revision = val
+
+    @hybrid_property
+    def locked(self):
+        # always should return [user_id, timelocked]
+        if self._locked:
+            _lock_info = self._locked.split(':')
+            return int(_lock_info[0]), _lock_info[1]
+        return [None, None]
+
+    @locked.setter
+    def locked(self, val):
+        if val and isinstance(val, (list, tuple)):
+            self._locked = ':'.join(map(str, val))
+        else:
+            self._locked = None
+
+    @hybrid_property
+    def changeset_cache(self):
+        from rhodecode.lib.vcs.backends.base import EmptyChangeset
+        dummy = EmptyChangeset().__json__()
+        if not self._changeset_cache:
+            return dummy
+        try:
+            return json.loads(self._changeset_cache)
+        except TypeError:
+            return dummy
+
+    @changeset_cache.setter
+    def changeset_cache(self, val):
+        try:
+            self._changeset_cache = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def normalize_repo_name(cls, repo_name):
+        """
+        Normalizes os specific repo_name to the format internally stored inside
+        dabatabase using URL_SEP
+
+        :param cls:
+        :param repo_name:
+        """
+        return cls.url_sep().join(repo_name.split(os.sep))
+
+    @classmethod
+    def get_by_repo_name(cls, repo_name):
+        q = Session().query(cls).filter(cls.repo_name == repo_name)
+        q = q.options(joinedload(Repository.fork))\
+                .options(joinedload(Repository.user))\
+                .options(joinedload(Repository.group))
+        return q.scalar()
+
+    @classmethod
+    def get_by_full_path(cls, repo_full_path):
+        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
+        repo_name = cls.normalize_repo_name(repo_name)
+        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
+
+    @classmethod
+    def get_repo_forks(cls, repo_id):
+        return cls.query().filter(Repository.fork_id == repo_id)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all repos are stored
+
+        :param cls:
+        """
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == cls.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def forks(self):
+        """
+        Return forks of this repo
+        """
+        return Repository.get_repo_forks(self.repo_id)
+
+    @property
+    def parent(self):
+        """
+        Returns fork parent
+        """
+        return self.fork
+
+    @property
+    def just_name(self):
+        return self.repo_name.split(Repository.url_sep())[-1]
+
+    @property
+    def groups_with_parents(self):
+        groups = []
+        if self.group is None:
+            return groups
+
+        cur_gr = self.group
+        groups.insert(0, cur_gr)
+        while 1:
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            groups.insert(0, gr)
+
+        return groups
+
+    @property
+    def groups_and_repo(self):
+        return self.groups_with_parents, self.just_name, self.repo_name
+
+    @LazyProperty
+    def repo_path(self):
+        """
+        Returns base full path for that repository means where it actually
+        exists on a filesystem
+        """
+        q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+                                              Repository.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def repo_full_path(self):
+        p = [self.repo_path]
+        # we need to split the name by / since this is how we store the
+        # names in the database, but that eventually needs to be converted
+        # into a valid system path
+        p += self.repo_name.split(Repository.url_sep())
+        return os.path.join(*map(safe_unicode, p))
+
+    @property
+    def cache_keys(self):
+        """
+        Returns associated cache keys for that repo
+        """
+        return CacheInvalidation.query()\
+            .filter(CacheInvalidation.cache_args == self.repo_name)\
+            .order_by(CacheInvalidation.cache_key)\
+            .all()
+
+    def get_new_name(self, repo_name):
+        """
+        returns new full repository name based on assigned group and new new
+
+        :param group_name:
+        """
+        path_prefix = self.group.full_path_splitted if self.group else []
+        return Repository.url_sep().join(path_prefix + [repo_name])
+
+    @property
+    def _ui(self):
+        """
+        Creates an db based ui object for this repository
+        """
+        from rhodecode.lib.utils import make_ui
+        return make_ui('db', clear_session=False)
+
+    @classmethod
+    def is_valid(cls, repo_name):
+        """
+        returns True if given repo name is a valid filesystem repository
+
+        :param cls:
+        :param repo_name:
+        """
+        from rhodecode.lib.utils import is_valid_repo
+
+        return is_valid_repo(repo_name, cls.base_path())
+
+    def get_api_data(self):
+        """
+        Common function for generating repo api data
+
+        """
+        repo = self
+        data = dict(
+            repo_id=repo.repo_id,
+            repo_name=repo.repo_name,
+            repo_type=repo.repo_type,
+            clone_uri=repo.clone_uri,
+            private=repo.private,
+            created_on=repo.created_on,
+            description=repo.description,
+            landing_rev=repo.landing_rev,
+            owner=repo.user.username,
+            fork_of=repo.fork.repo_name if repo.fork else None,
+            enable_statistics=repo.enable_statistics,
+            enable_locking=repo.enable_locking,
+            enable_downloads=repo.enable_downloads,
+            last_changeset=repo.changeset_cache,
+            locked_by=User.get(self.locked[0]).get_api_data() \
+                if self.locked[0] else None,
+            locked_date=time_to_datetime(self.locked[1]) \
+                if self.locked[1] else None
+        )
+        rc_config = RhodeCodeSetting.get_app_settings()
+        repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
+        if repository_fields:
+            for f in self.extra_fields:
+                data[f.field_key_prefixed] = f.field_value
+
+        return data
+
+    @classmethod
+    def lock(cls, repo, user_id, lock_time=None):
+        if not lock_time:
+            lock_time = time.time()
+        repo.locked = [user_id, lock_time]
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def unlock(cls, repo):
+        repo.locked = None
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def getlock(cls, repo):
+        return repo.locked
+
+    @property
+    def last_db_change(self):
+        return self.updated_on
+
+    def clone_url(self, **override):
+        from pylons import url
+        qualified_home_url = url('home', qualified=True)
+
+        uri_tmpl = None
+        if 'uri_tmpl' in override:
+            uri_tmpl = override['uri_tmpl']
+            del override['uri_tmpl']
+
+        # we didn't override our tmpl from **overrides
+        if not uri_tmpl:
+            uri_tmpl = self.DEFAULT_CLONE_URI
+            try:
+                from pylons import tmpl_context as c
+                uri_tmpl = c.clone_uri_tmpl
+            except Exception:
+                # in any case if we call this outside of request context,
+                # ie, not having tmpl_context set up
+                pass
+
+        return get_clone_url(uri_tmpl=uri_tmpl,
+                             qualifed_home_url=qualified_home_url,
+                             repo_name=self.repo_name,
+                             repo_id=self.repo_id, **override)
+
+    #==========================================================================
+    # SCM PROPERTIES
+    #==========================================================================
+
+    def get_changeset(self, rev=None):
+        return get_changeset_safe(self.scm_instance, rev)
+
+    def get_landing_changeset(self):
+        """
+        Returns landing changeset, or if that doesn't exist returns the tip
+        """
+        _rev_type, _rev = self.landing_rev
+        cs = self.get_changeset(_rev)
+        if isinstance(cs, EmptyChangeset):
+            return self.get_changeset()
+        return cs
+
+    def update_changeset_cache(self, cs_cache=None):
+        """
+        Update cache of last changeset for repository, keys should be::
+
+            short_id
+            raw_id
+            revision
+            message
+            date
+            author
+
+        :param cs_cache:
+        """
+        from rhodecode.lib.vcs.backends.base import BaseChangeset
+        if cs_cache is None:
+            cs_cache = EmptyChangeset()
+            # use no-cache version here
+            scm_repo = self.scm_instance_no_cache()
+            if scm_repo:
+                cs_cache = scm_repo.get_changeset()
+
+        if isinstance(cs_cache, BaseChangeset):
+            cs_cache = cs_cache.__json__()
+
+        if (cs_cache != self.changeset_cache or not self.changeset_cache):
+            _default = datetime.datetime.fromtimestamp(0)
+            last_change = cs_cache.get('date') or _default
+            log.debug('updated repo %s with new cs cache %s'
+                      % (self.repo_name, cs_cache))
+            self.updated_on = last_change
+            self.changeset_cache = cs_cache
+            Session().add(self)
+            Session().commit()
+        else:
+            log.debug('Skipping repo:%s already with latest changes'
+                      % self.repo_name)
+
+    @property
+    def tip(self):
+        return self.get_changeset('tip')
+
+    @property
+    def author(self):
+        return self.tip.author
+
+    @property
+    def last_change(self):
+        return self.scm_instance.last_change
+
+    def get_comments(self, revisions=None):
+        """
+        Returns comments for this repository grouped by revisions
+
+        :param revisions: filter query by revisions only
+        """
+        cmts = ChangesetComment.query()\
+            .filter(ChangesetComment.repo == self)
+        if revisions:
+            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
+        grouped = collections.defaultdict(list)
+        for cmt in cmts.all():
+            grouped[cmt.revision].append(cmt)
+        return grouped
+
+    def statuses(self, revisions=None):
+        """
+        Returns statuses for this repository
+
+        :param revisions: list of revisions to get statuses for
+        """
+
+        statuses = ChangesetStatus.query()\
+            .filter(ChangesetStatus.repo == self)\
+            .filter(ChangesetStatus.version == 0)
+        if revisions:
+            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
+        grouped = {}
+
+        #maybe we have open new pullrequest without a status ?
+        stat = ChangesetStatus.STATUS_UNDER_REVIEW
+        status_lbl = ChangesetStatus.get_status_lbl(stat)
+        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
+            for rev in pr.revisions:
+                pr_id = pr.pull_request_id
+                pr_repo = pr.other_repo.repo_name
+                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
+
+        for stat in statuses.all():
+            pr_id = pr_repo = None
+            if stat.pull_request:
+                pr_id = stat.pull_request.pull_request_id
+                pr_repo = stat.pull_request.other_repo.repo_name
+            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
+                                      pr_id, pr_repo]
+        return grouped
+
+    def _repo_size(self):
+        from rhodecode.lib import helpers as h
+        log.debug('calculating repository size...')
+        return h.format_byte_size(self.scm_instance.size)
+
+    #==========================================================================
+    # SCM CACHE INSTANCE
+    #==========================================================================
+
+    def set_invalidate(self):
+        """
+        Mark caches of this repo as invalid.
+        """
+        CacheInvalidation.set_invalidate(self.repo_name)
+
+    def scm_instance_no_cache(self):
+        return self.__get_instance()
+
+    @property
+    def scm_instance(self):
+        import rhodecode
+        full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
+        if full_cache:
+            return self.scm_instance_cached()
+        return self.__get_instance()
+
+    def scm_instance_cached(self, valid_cache_keys=None):
+        @cache_region('long_term')
+        def _c(repo_name):
+            return self.__get_instance()
+        rn = self.repo_name
+
+        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
+        if not valid:
+            log.debug('Cache for %s invalidated, getting new object' % (rn))
+            region_invalidate(_c, None, rn)
+        else:
+            log.debug('Getting obj for %s from cache' % (rn))
+        return _c(rn)
+
+    def __get_instance(self):
+        repo_full_path = self.repo_full_path
+        try:
+            alias = get_scm(repo_full_path)[0]
+            log.debug('Creating instance of %s repository from %s'
+                      % (alias, repo_full_path))
+            backend = get_backend(alias)
+        except VCSError:
+            log.error(traceback.format_exc())
+            log.error('Perhaps this repository is in db and not in '
+                      'filesystem run rescan repositories with '
+                      '"destroy old data " option from admin panel')
+            return
+
+        if alias == 'hg':
+
+            repo = backend(safe_str(repo_full_path), create=False,
+                           baseui=self._ui)
+            # skip hidden web repository
+            if repo._get_hidden():
+                return
+        else:
+            repo = backend(repo_full_path, create=False)
+
+        return repo
+
+    def __json__(self):
+        return dict(landing_rev = self.landing_rev)
+
+class RepoGroup(Base, BaseModel):
+    __tablename__ = 'groups'
+    __table_args__ = (
+        UniqueConstraint('group_name', 'group_parent_id'),
+        CheckConstraint('group_id != group_parent_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    __mapper_args__ = {'order_by': 'group_name'}
+
+    SEP = ' &raquo; '
+
+    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
+    group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    parent_group = relationship('RepoGroup', remote_side=group_id)
+    user = relationship('User')
+
+    def __init__(self, group_name='', parent_group=None):
+        self.group_name = group_name
+        self.parent_group = parent_group
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
+                                      self.group_name)
+
+    @classmethod
+    def _generate_choice(cls, repo_group):
+        from webhelpers.html import literal as _literal
+        _name = lambda k: _literal(cls.SEP.join(k))
+        return repo_group.group_id, _name(repo_group.full_path_splitted)
+
+    @classmethod
+    def groups_choices(cls, groups=None, show_empty_group=True):
+        if not groups:
+            groups = cls.query().all()
+
+        repo_groups = []
+        if show_empty_group:
+            repo_groups = [('-1', u'-- %s --' % _('top level'))]
+
+        repo_groups.extend([cls._generate_choice(x) for x in groups])
+
+        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0])
+        return repo_groups
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+        if case_insensitive:
+            gr = cls.query()\
+                .filter(cls.group_name.ilike(group_name))
+        else:
+            gr = cls.query()\
+                .filter(cls.group_name == group_name)
+        if cache:
+            gr = gr.options(FromCache(
+                            "sql_cache_short",
+                            "get_group_%s" % _hash_key(group_name)
+                            )
+            )
+        return gr.scalar()
+
+    @property
+    def parents(self):
+        parents_recursion_limit = 5
+        groups = []
+        if self.parent_group is None:
+            return groups
+        cur_gr = self.parent_group
+        groups.insert(0, cur_gr)
+        cnt = 0
+        while 1:
+            cnt += 1
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            if cnt == parents_recursion_limit:
+                # this will prevent accidental infinit loops
+                log.error('group nested more than %s' %
+                          parents_recursion_limit)
+                break
+
+            groups.insert(0, gr)
+        return groups
+
+    @property
+    def children(self):
+        return RepoGroup.query().filter(RepoGroup.parent_group == self)
+
+    @property
+    def name(self):
+        return self.group_name.split(RepoGroup.url_sep())[-1]
+
+    @property
+    def full_path(self):
+        return self.group_name
+
+    @property
+    def full_path_splitted(self):
+        return self.group_name.split(RepoGroup.url_sep())
+
+    @property
+    def repositories(self):
+        return Repository.query()\
+                .filter(Repository.group == self)\
+                .order_by(Repository.repo_name)
+
+    @property
+    def repositories_recursive_count(self):
+        cnt = self.repositories.count()
+
+        def children_count(group):
+            cnt = 0
+            for child in group.children:
+                cnt += child.repositories.count()
+                cnt += children_count(child)
+            return cnt
+
+        return cnt + children_count(self)
+
+    def _recursive_objects(self, include_repos=True):
+        all_ = []
+
+        def _get_members(root_gr):
+            if include_repos:
+                for r in root_gr.repositories:
+                    all_.append(r)
+            childs = root_gr.children.all()
+            if childs:
+                for gr in childs:
+                    all_.append(gr)
+                    _get_members(gr)
+
+        _get_members(self)
+        return [self] + all_
+
+    def recursive_groups_and_repos(self):
+        """
+        Recursive return all groups, with repositories in those groups
+        """
+        return self._recursive_objects()
+
+    def recursive_groups(self):
+        """
+        Returns all children groups for this group including children of children
+        """
+        return self._recursive_objects(include_repos=False)
+
+    def get_new_name(self, group_name):
+        """
+        returns new full group name based on parent and new name
+
+        :param group_name:
+        """
+        path_prefix = (self.parent_group.full_path_splitted if
+                       self.parent_group else [])
+        return RepoGroup.url_sep().join(path_prefix + [group_name])
+
+    def get_api_data(self):
+        """
+        Common function for generating api data
+
+        """
+        group = self
+        data = dict(
+            group_id=group.group_id,
+            group_name=group.group_name,
+            group_description=group.group_description,
+            parent_group=group.parent_group.group_name if group.parent_group else None,
+            repositories=[x.repo_name for x in group.repositories],
+            owner=group.user.username
+        )
+        return data
+
+
+class Permission(Base, BaseModel):
+    __tablename__ = 'permissions'
+    __table_args__ = (
+        Index('p_perm_name_idx', 'permission_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PERMS = [
+        ('hg.admin', _('RhodeCode Administrator')),
+
+        ('repository.none', _('Repository no access')),
+        ('repository.read', _('Repository read access')),
+        ('repository.write', _('Repository write access')),
+        ('repository.admin', _('Repository admin access')),
+
+        ('group.none', _('Repository group no access')),
+        ('group.read', _('Repository group read access')),
+        ('group.write', _('Repository group write access')),
+        ('group.admin', _('Repository group admin access')),
+
+        ('usergroup.none', _('User group no access')),
+        ('usergroup.read', _('User group read access')),
+        ('usergroup.write', _('User group write access')),
+        ('usergroup.admin', _('User group admin access')),
+
+        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
+        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
+
+        ('hg.usergroup.create.false', _('User Group creation disabled')),
+        ('hg.usergroup.create.true', _('User Group creation enabled')),
+
+        ('hg.create.none', _('Repository creation disabled')),
+        ('hg.create.repository', _('Repository creation enabled')),
+        ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
+        ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
+
+        ('hg.fork.none', _('Repository forking disabled')),
+        ('hg.fork.repository', _('Repository forking enabled')),
+
+        ('hg.register.none', _('Registration disabled')),
+        ('hg.register.manual_activate', _('User Registration with manual account activation')),
+        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
+
+        ('hg.extern_activate.manual', _('Manual activation of external account')),
+        ('hg.extern_activate.auto', _('Automatic activation of external account')),
+
+    ]
+
+    #definition of system default permissions for DEFAULT user
+    DEFAULT_USER_PERMISSIONS = [
+        'repository.read',
+        'group.read',
+        'usergroup.read',
+        'hg.create.repository',
+        'hg.create.write_on_repogroup.true',
+        'hg.fork.repository',
+        'hg.register.manual_activate',
+        'hg.extern_activate.auto',
+    ]
+
+    # defines which permissions are more important higher the more important
+    # Weight defines which permissions are more important.
+    # The higher number the more important.
+    PERM_WEIGHTS = {
+        'repository.none': 0,
+        'repository.read': 1,
+        'repository.write': 3,
+        'repository.admin': 4,
+
+        'group.none': 0,
+        'group.read': 1,
+        'group.write': 3,
+        'group.admin': 4,
+
+        'usergroup.none': 0,
+        'usergroup.read': 1,
+        'usergroup.write': 3,
+        'usergroup.admin': 4,
+        'hg.repogroup.create.false': 0,
+        'hg.repogroup.create.true': 1,
+
+        'hg.usergroup.create.false': 0,
+        'hg.usergroup.create.true': 1,
+
+        'hg.fork.none': 0,
+        'hg.fork.repository': 1,
+        'hg.create.none': 0,
+        'hg.create.repository': 1
+    }
+
+    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__, self.permission_id, self.permission_name
+        )
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.permission_name == key).scalar()
+
+    @classmethod
+    def get_default_perms(cls, default_user_id):
+        q = Session().query(UserRepoToPerm, Repository, cls)\
+         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
+         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_group_perms(cls, default_user_id):
+        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
+         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
+         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_user_group_perms(cls, default_user_id):
+        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
+         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
+         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserUserGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+
+class UserRepoToPerm(Base, BaseModel):
+    __tablename__ = 'repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    repository = relationship('Repository')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, repository, permission):
+        n = cls()
+        n.user = user
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.repository)
+
+
+class UserUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    user_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, user_group, permission):
+        n = cls()
+        n.user = user
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.user_group)
+
+
+class UserToPerm(Base, BaseModel):
+    __tablename__ = 'user_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    permission = relationship('Permission', lazy='joined')
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.permission)
+
+
+class UserGroupRepoToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    repository = relationship('Repository')
+
+    @classmethod
+    def create(cls, users_group, repository, permission):
+        n = cls()
+        n.users_group = users_group
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
+
+
+class UserGroupUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_group_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
+        CheckConstraint('target_user_group_id != user_group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
+    user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, target_user_group, user_group, permission):
+        n = cls()
+        n.target_user_group = target_user_group
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
+
+
+class UserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'permission_id',),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+
+class UserRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    group = relationship('RepoGroup')
+    permission = relationship('Permission')
+
+
+class UserGroupRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    group = relationship('RepoGroup')
+
+
+class Statistics(Base, BaseModel):
+    __tablename__ = 'statistics'
+    __table_args__ = (
+         UniqueConstraint('repository_id'),
+         {'extend_existing': True, 'mysql_engine': 'InnoDB',
+          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
+    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
+    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
+    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
+
+    repository = relationship('Repository', single_parent=True)
+
+
+class UserFollowing(Base, BaseModel):
+    __tablename__ = 'user_followings'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'follows_repository_id'),
+        UniqueConstraint('user_id', 'follows_user_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
+    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+
+    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
+
+    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
+    follows_repository = relationship('Repository', order_by='Repository.repo_name')
+
+    @classmethod
+    def get_repo_followers(cls, repo_id):
+        return cls.query().filter(cls.follows_repo_id == repo_id)
+
+
+class CacheInvalidation(Base, BaseModel):
+    __tablename__ = 'cache_invalidation'
+    __table_args__ = (
+        UniqueConstraint('cache_key'),
+        Index('key_idx', 'cache_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    # cache_id, not used
+    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    # cache_key as created by _get_cache_key
+    cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    # cache_args is a repo_name
+    cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    # instance sets cache_active True when it is caching,
+    # other instances set cache_active to False to indicate that this cache is invalid
+    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+
+    def __init__(self, cache_key, repo_name=''):
+        self.cache_key = cache_key
+        self.cache_args = repo_name
+        self.cache_active = False
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
+                            self.cache_id, self.cache_key, self.cache_active)
+
+    def _cache_key_partition(self):
+        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
+        return prefix, repo_name, suffix
+
+    def get_prefix(self):
+        """
+        get prefix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[0]
+
+    def get_suffix(self):
+        """
+        get suffix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[2]
+
+    @classmethod
+    def clear_cache(cls):
+        """
+        Delete all cache keys from database.
+        Should only be run when all instances are down and all entries thus stale.
+        """
+        cls.query().delete()
+        Session().commit()
+
+    @classmethod
+    def _get_cache_key(cls, key):
+        """
+        Wrapper for generating a unique cache key for this instance and "key".
+        key must / will start with a repo_name which will be stored in .cache_args .
+        """
+        import rhodecode
+        prefix = rhodecode.CONFIG.get('instance_id', '')
+        return "%s%s" % (prefix, key)
+
+    @classmethod
+    def set_invalidate(cls, repo_name, delete=False):
+        """
+        Mark all caches of a repo as invalid in the database.
+        """
+        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
+        log.debug('for repo %s got %s invalidation objects' % (repo_name, inv_objs))
+        try:
+            for inv_obj in inv_objs:
+                log.debug('marking %s key for invalidation based on repo_name=%s'
+                          % (inv_obj, safe_str(repo_name)))
+                if delete:
+                    Session().delete(inv_obj)
+                else:
+                    inv_obj.cache_active = False
+                    Session().add(inv_obj)
+            Session().commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+
+    @classmethod
+    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
+        """
+        Mark this cache key as active and currently cached.
+        Return True if the existing cache registration still was valid.
+        Return False to indicate that it had been invalidated and caches should be refreshed.
+        """
+
+        key = (repo_name + '_' + kind) if kind else repo_name
+        cache_key = cls._get_cache_key(key)
+
+        if valid_cache_keys and cache_key in valid_cache_keys:
+            return True
+
+        try:
+            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
+            if not inv_obj:
+                inv_obj = CacheInvalidation(cache_key, repo_name)
+            was_valid = inv_obj.cache_active
+            inv_obj.cache_active = True
+            Session().add(inv_obj)
+            Session().commit()
+            return was_valid
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+            return False
+
+    @classmethod
+    def get_valid_cache_keys(cls):
+        """
+        Return opaque object with information of which caches still are valid
+        and can be used without checking for invalidation.
+        """
+        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
+
+
+class ChangesetComment(Base, BaseModel):
+    __tablename__ = 'changeset_comments'
+    __table_args__ = (
+        Index('cc_revision_idx', 'revision'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    revision = Column('revision', String(40), nullable=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+    line_no = Column('line_no', Unicode(10), nullable=True)
+    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
+    f_path = Column('f_path', Unicode(1000), nullable=True)
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+    text = Column('text', UnicodeText(25000), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    @classmethod
+    def get_users(cls, revision=None, pull_request_id=None):
+        """
+        Returns user associated with this ChangesetComment. ie those
+        who actually commented
+
+        :param cls:
+        :param revision:
+        """
+        q = Session().query(User)\
+                .join(ChangesetComment.author)
+        if revision:
+            q = q.filter(cls.revision == revision)
+        elif pull_request_id:
+            q = q.filter(cls.pull_request_id == pull_request_id)
+        return q.all()
+
+
+class ChangesetStatus(Base, BaseModel):
+    __tablename__ = 'changeset_statuses'
+    __table_args__ = (
+        Index('cs_revision_idx', 'revision'),
+        Index('cs_version_idx', 'version'),
+        UniqueConstraint('repo_id', 'revision', 'version'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
+    STATUS_APPROVED = 'approved'
+    STATUS_REJECTED = 'rejected'
+    STATUS_UNDER_REVIEW = 'under_review'
+
+    STATUSES = [
+        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
+        (STATUS_APPROVED, _("Approved")),
+        (STATUS_REJECTED, _("Rejected")),
+        (STATUS_UNDER_REVIEW, _("Under Review")),
+    ]
+
+    changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    revision = Column('revision', String(40), nullable=False)
+    status = Column('status', String(128), nullable=False, default=DEFAULT)
+    changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
+    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
+    version = Column('version', Integer(), nullable=False, default=0)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    comment = relationship('ChangesetComment', lazy='joined')
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__,
+            self.status, self.author
+        )
+
+    @classmethod
+    def get_status_lbl(cls, value):
+        return dict(cls.STATUSES).get(value)
+
+    @property
+    def status_lbl(self):
+        return ChangesetStatus.get_status_lbl(self.status)
+
+
+class PullRequest(Base, BaseModel):
+    __tablename__ = 'pull_requests'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    # values for .status
+    STATUS_NEW = u'new'
+    STATUS_OPEN = u'open'
+    STATUS_CLOSED = u'closed'
+
+    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
+    title = Column('title', Unicode(256), nullable=True)
+    description = Column('description', UnicodeText(10240), nullable=True)
+    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    _revisions = Column('revisions', UnicodeText(20500))  # 500 revisions max
+    org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    org_ref = Column('org_ref', Unicode(256), nullable=False)
+    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    other_ref = Column('other_ref', Unicode(256), nullable=False)
+
+    @hybrid_property
+    def revisions(self):
+        return self._revisions.split(':')
+
+    @revisions.setter
+    def revisions(self, val):
+        self._revisions = ':'.join(val)
+
+    @property
+    def org_ref_parts(self):
+        return self.org_ref.split(':')
+
+    @property
+    def other_ref_parts(self):
+        return self.other_ref.split(':')
+
+    author = relationship('User', lazy='joined')
+    reviewers = relationship('PullRequestReviewers',
+                             cascade="all, delete, delete-orphan")
+    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
+    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
+    statuses = relationship('ChangesetStatus')
+    comments = relationship('ChangesetComment',
+                             cascade="all, delete, delete-orphan")
+
+    def is_closed(self):
+        return self.status == self.STATUS_CLOSED
+
+    @property
+    def last_review_status(self):
+        return self.statuses[-1].status if self.statuses else ''
+
+    def __json__(self):
+        return dict(
+            revisions=self.revisions
+        )
+
+
+class PullRequestReviewers(Base, BaseModel):
+    __tablename__ = 'pull_request_reviewers'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    def __init__(self, user=None, pull_request=None):
+        self.user = user
+        self.pull_request = pull_request
+
+    pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
+
+    user = relationship('User')
+    pull_request = relationship('PullRequest')
+
+
+class Notification(Base, BaseModel):
+    __tablename__ = 'notifications'
+    __table_args__ = (
+        Index('notification_type_idx', 'type'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    TYPE_CHANGESET_COMMENT = u'cs_comment'
+    TYPE_MESSAGE = u'message'
+    TYPE_MENTION = u'mention'
+    TYPE_REGISTRATION = u'registration'
+    TYPE_PULL_REQUEST = u'pull_request'
+    TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
+
+    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
+    subject = Column('subject', Unicode(512), nullable=True)
+    body = Column('body', UnicodeText(50000), nullable=True)
+    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    type_ = Column('type', Unicode(256))
+
+    created_by_user = relationship('User')
+    notifications_to_users = relationship('UserNotification', lazy='joined',
+                                          cascade="all, delete, delete-orphan")
+
+    @property
+    def recipients(self):
+        return [x.user for x in UserNotification.query()\
+                .filter(UserNotification.notification == self)\
+                .order_by(UserNotification.user_id.asc()).all()]
+
+    @classmethod
+    def create(cls, created_by, subject, body, recipients, type_=None):
+        if type_ is None:
+            type_ = Notification.TYPE_MESSAGE
+
+        notification = cls()
+        notification.created_by_user = created_by
+        notification.subject = subject
+        notification.body = body
+        notification.type_ = type_
+        notification.created_on = datetime.datetime.now()
+
+        for u in recipients:
+            assoc = UserNotification()
+            assoc.notification = notification
+            u.notifications.append(assoc)
+        Session().add(notification)
+        return notification
+
+    @property
+    def description(self):
+        from rhodecode.model.notification import NotificationModel
+        return NotificationModel().make_description(self)
+
+
+class UserNotification(Base, BaseModel):
+    __tablename__ = 'user_to_notification'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'notification_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
+    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
+    read = Column('read', Boolean, default=False)
+    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
+
+    user = relationship('User', lazy="joined")
+    notification = relationship('Notification', lazy="joined",
+                                order_by=lambda: Notification.created_on.desc(),)
+
+    def mark_as_read(self):
+        self.read = True
+        Session().add(self)
+
+
+class Gist(Base, BaseModel):
+    __tablename__ = 'gists'
+    __table_args__ = (
+        Index('g_gist_access_id_idx', 'gist_access_id'),
+        Index('g_created_on_idx', 'created_on'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    GIST_PUBLIC = u'public'
+    GIST_PRIVATE = u'private'
+    DEFAULT_FILENAME = u'gistfile1.txt'
+
+    gist_id = Column('gist_id', Integer(), primary_key=True)
+    gist_access_id = Column('gist_access_id', Unicode(250))
+    gist_description = Column('gist_description', UnicodeText(1024))
+    gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
+    gist_expires = Column('gist_expires', Float(53), nullable=False)
+    gist_type = Column('gist_type', Unicode(128), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    owner = relationship('User')
+
+    def __repr__(self):
+        return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
+
+    @classmethod
+    def get_or_404(cls, id_):
+        res = cls.query().filter(cls.gist_access_id == id_).scalar()
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def get_by_access_id(cls, gist_access_id):
+        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
+
+    def gist_url(self):
+        import rhodecode
+        alias_url = rhodecode.CONFIG.get('gist_alias_url')
+        if alias_url:
+            return alias_url.replace('{gistid}', self.gist_access_id)
+
+        from pylons import url
+        return url('gist', gist_id=self.gist_access_id, qualified=True)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all gists are stored
+
+        :param cls:
+        """
+        from rhodecode.model.gist import GIST_STORE_LOC
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == URL_SEP)
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
+
+    def get_api_data(self):
+        """
+        Common function for generating gist related data for API
+        """
+        gist = self
+        data = dict(
+            gist_id=gist.gist_id,
+            type=gist.gist_type,
+            access_id=gist.gist_access_id,
+            description=gist.gist_description,
+            url=gist.gist_url(),
+            expires=gist.gist_expires,
+            created_on=gist.created_on,
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+        )
+        data.update(self.get_api_data())
+        return data
+    ## SCM functions
+
+    @property
+    def scm_instance(self):
+        from rhodecode.lib.vcs import get_repo
+        base_path = self.base_path()
+        return get_repo(os.path.join(*map(safe_str,
+                                          [base_path, self.gist_access_id])))
+
+
+class DbMigrateVersion(Base, BaseModel):
+    __tablename__ = 'db_migrate_version'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    repository_id = Column('repository_id', String(250), primary_key=True)
+    repository_path = Column('repository_path', Text)
+    version = Column('version', Integer)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/schema/db_2_2_3.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,2494 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db
+~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import os
+import time
+import logging
+import datetime
+import traceback
+import hashlib
+import collections
+import functools
+
+from sqlalchemy import *
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
+from sqlalchemy.exc import DatabaseError
+from beaker.cache import cache_region, region_invalidate
+from webob.exc import HTTPNotFound
+
+from pylons.i18n.translation import lazy_ugettext as _
+
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
+
+from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
+    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \
+    get_clone_url
+from rhodecode.lib.compat import json
+from rhodecode.lib.caching_query import FromCache
+
+from rhodecode.model.meta import Base, Session
+
+URL_SEP = '/'
+log = logging.getLogger(__name__)
+
+#==============================================================================
+# BASE CLASSES
+#==============================================================================
+
+_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
+
+
+class BaseModel(object):
+    """
+    Base Model for all classess
+    """
+
+    @classmethod
+    def _get_keys(cls):
+        """return column names for this model """
+        return class_mapper(cls).c.keys()
+
+    def get_dict(self):
+        """
+        return dict with keys and values corresponding
+        to this model data """
+
+        d = {}
+        for k in self._get_keys():
+            d[k] = getattr(self, k)
+
+        # also use __json__() if present to get additional fields
+        _json_attr = getattr(self, '__json__', None)
+        if _json_attr:
+            # update with attributes from __json__
+            if callable(_json_attr):
+                _json_attr = _json_attr()
+            for k, val in _json_attr.iteritems():
+                d[k] = val
+        return d
+
+    def get_appstruct(self):
+        """return list with keys and values tupples corresponding
+        to this model data """
+
+        l = []
+        for k in self._get_keys():
+            l.append((k, getattr(self, k),))
+        return l
+
+    def populate_obj(self, populate_dict):
+        """populate model with data from given populate_dict"""
+
+        for k in self._get_keys():
+            if k in populate_dict:
+                setattr(self, k, populate_dict[k])
+
+    @classmethod
+    def query(cls):
+        return Session().query(cls)
+
+    @classmethod
+    def get(cls, id_):
+        if id_:
+            return cls.query().get(id_)
+
+    @classmethod
+    def get_or_404(cls, id_):
+        try:
+            id_ = int(id_)
+        except (TypeError, ValueError):
+            raise HTTPNotFound
+
+        res = cls.query().get(id_)
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def getAll(cls):
+        # deprecated and left for backward compatibility
+        return cls.get_all()
+
+    @classmethod
+    def get_all(cls):
+        return cls.query().all()
+
+    @classmethod
+    def delete(cls, id_):
+        obj = cls.query().get(id_)
+        Session().delete(obj)
+
+    def __repr__(self):
+        if hasattr(self, '__unicode__'):
+            # python repr needs to return str
+            try:
+                return safe_str(self.__unicode__())
+            except UnicodeDecodeError:
+                pass
+        return '<DB:%s>' % (self.__class__.__name__)
+
+
+class RhodeCodeSetting(Base, BaseModel):
+    __tablename__ = 'rhodecode_settings'
+    __table_args__ = (
+        UniqueConstraint('app_settings_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    SETTINGS_TYPES = {
+        'str': safe_str,
+        'int': safe_int,
+        'unicode': safe_unicode,
+        'bool': str2bool,
+        'list': functools.partial(aslist, sep=',')
+    }
+    DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
+
+    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None)
+    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+
+    def __init__(self, key='', val='', type='unicode'):
+        self.app_settings_name = key
+        self.app_settings_value = val
+        self.app_settings_type = type
+
+    @validates('_app_settings_value')
+    def validate_settings_value(self, key, val):
+        assert type(val) == unicode
+        return val
+
+    @hybrid_property
+    def app_settings_value(self):
+        v = self._app_settings_value
+        _type = self.app_settings_type
+        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
+        return converter(v)
+
+    @app_settings_value.setter
+    def app_settings_value(self, val):
+        """
+        Setter that will always make sure we use unicode in app_settings_value
+
+        :param val:
+        """
+        self._app_settings_value = safe_unicode(val)
+
+    @hybrid_property
+    def app_settings_type(self):
+        return self._app_settings_type
+
+    @app_settings_type.setter
+    def app_settings_type(self, val):
+        if val not in self.SETTINGS_TYPES:
+            raise Exception('type must be one of %s got %s'
+                            % (self.SETTINGS_TYPES.keys(), val))
+        self._app_settings_type = val
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (
+            self.__class__.__name__,
+            self.app_settings_name, self.app_settings_value, self.app_settings_type
+        )
+
+    @classmethod
+    def get_by_name(cls, key):
+        return cls.query()\
+            .filter(cls.app_settings_name == key).scalar()
+
+    @classmethod
+    def get_by_name_or_create(cls, key, val='', type='unicode'):
+        res = cls.get_by_name(key)
+        if not res:
+            res = cls(key, val, type)
+        return res
+
+    @classmethod
+    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
+        """
+        Creates or updates RhodeCode setting. If updates is triggered it will only
+        update parameters that are explicityl set Optional instance will be skipped
+
+        :param key:
+        :param val:
+        :param type:
+        :return:
+        """
+        res = cls.get_by_name(key)
+        if not res:
+            val = Optional.extract(val)
+            type = Optional.extract(type)
+            res = cls(key, val, type)
+        else:
+            res.app_settings_name = key
+            if not isinstance(val, Optional):
+                # update if set
+                res.app_settings_value = val
+            if not isinstance(type, Optional):
+                # update if set
+                res.app_settings_type = type
+        return res
+
+    @classmethod
+    def get_app_settings(cls, cache=False):
+
+        ret = cls.query()
+
+        if cache:
+            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
+
+        if not ret:
+            raise Exception('Could not get application settings !')
+        settings = {}
+        for each in ret:
+            settings['rhodecode_' + each.app_settings_name] = \
+                each.app_settings_value
+
+        return settings
+
+    @classmethod
+    def get_auth_plugins(cls, cache=False):
+        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
+        return auth_plugins
+
+    @classmethod
+    def get_auth_settings(cls, cache=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('auth_')).all()
+        fd = {}
+        for row in ret:
+            fd.update({row.app_settings_name: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('default_')).all()
+        fd = {}
+        for row in ret:
+            key = row.app_settings_name
+            if strip_prefix:
+                key = remove_prefix(key, prefix='default_')
+            fd.update({key: row.app_settings_value})
+
+        return fd
+
+    @classmethod
+    def get_server_info(cls):
+        import pkg_resources
+        import platform
+        import rhodecode
+        from rhodecode.lib.utils import check_git_version
+        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
+        info = {
+            'modules': sorted(mods, key=lambda k: k[0].lower()),
+            'py_version': platform.python_version(),
+            'platform': safe_unicode(platform.platform()),
+            'rhodecode_version': rhodecode.__version__,
+            'git_version': safe_unicode(check_git_version()),
+            'git_path': rhodecode.CONFIG.get('git_path')
+        }
+        return info
+
+
+class RhodeCodeUi(Base, BaseModel):
+    __tablename__ = 'rhodecode_ui'
+    __table_args__ = (
+        UniqueConstraint('ui_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    HOOK_UPDATE = 'changegroup.update'
+    HOOK_REPO_SIZE = 'changegroup.repo_size'
+    HOOK_PUSH = 'changegroup.push_logger'
+    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
+    HOOK_PULL = 'outgoing.pull_logger'
+    HOOK_PRE_PULL = 'preoutgoing.pre_pull'
+
+    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
+
+    # def __init__(self, section='', key='', value=''):
+    #     self.ui_section = section
+    #     self.ui_key = key
+    #     self.ui_value = value
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.ui_key == key).scalar()
+
+    @classmethod
+    def get_builtin_hooks(cls):
+        q = cls.query()
+        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        return q.all()
+
+    @classmethod
+    def get_custom_hooks(cls):
+        q = cls.query()
+        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
+                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
+                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+        q = q.filter(cls.ui_section == 'hooks')
+        return q.all()
+
+    @classmethod
+    def get_repos_location(cls):
+        return cls.get_by_key('/').ui_value
+
+    @classmethod
+    def create_or_update_hook(cls, key, val):
+        new_ui = cls.get_by_key(key) or cls()
+        new_ui.ui_section = 'hooks'
+        new_ui.ui_active = True
+        new_ui.ui_key = key
+        new_ui.ui_value = val
+
+        Session().add(new_ui)
+
+    def __repr__(self):
+        return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
+                                    self.ui_key, self.ui_value)
+
+
+class User(Base, BaseModel):
+    __tablename__ = 'users'
+    __table_args__ = (
+        UniqueConstraint('username'), UniqueConstraint('email'),
+        Index('u_username_idx', 'username'),
+        Index('u_email_idx', 'email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    DEFAULT_USER = 'default'
+    DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
+
+    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+    name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
+    extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    _user_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data
+
+    user_log = relationship('UserLog')
+    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
+
+    repositories = relationship('Repository')
+    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
+    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
+
+    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
+
+    group_member = relationship('UserGroupMember', cascade='all')
+
+    notifications = relationship('UserNotification', cascade='all')
+    # notifications assigned to this user
+    user_created_notifications = relationship('Notification', cascade='all')
+    # comments created by this user
+    user_comments = relationship('ChangesetComment', cascade='all')
+    #extra emails for this user
+    user_emails = relationship('UserEmailMap', cascade='all')
+    #extra api keys
+    user_api_keys = relationship('UserApiKeys', cascade='all')
+
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+    @property
+    def firstname(self):
+        # alias for future
+        return self.name
+
+    @property
+    def emails(self):
+        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
+        return [self.email] + [x.email for x in other]
+
+    @property
+    def api_keys(self):
+        other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
+        return [self.api_key] + [x.api_key for x in other]
+
+    @property
+    def ip_addresses(self):
+        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
+        return [x.ip_addr for x in ret]
+
+    @property
+    def username_and_name(self):
+        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
+
+    @property
+    def full_name(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def full_name_or_username(self):
+        return ('%s %s' % (self.firstname, self.lastname)
+                if (self.firstname and self.lastname) else self.username)
+
+    @property
+    def full_contact(self):
+        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
+
+    @property
+    def short_contact(self):
+        return '%s %s' % (self.firstname, self.lastname)
+
+    @property
+    def is_admin(self):
+        return self.admin
+
+    @property
+    def AuthUser(self):
+        """
+        Returns instance of AuthUser for this user
+        """
+        from rhodecode.lib.auth import AuthUser
+        return AuthUser(user_id=self.user_id, api_key=self.api_key,
+                        username=self.username)
+
+    @hybrid_property
+    def user_data(self):
+        if not self._user_data:
+            return {}
+
+        try:
+            return json.loads(self._user_data)
+        except TypeError:
+            return {}
+
+    @user_data.setter
+    def user_data(self, val):
+        try:
+            self._user_data = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.user_id, self.username)
+
+    @classmethod
+    def get_by_username(cls, username, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.username.ilike(username))
+        else:
+            q = cls.query().filter(cls.username == username)
+
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(username)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get_by_api_key(cls, api_key, cache=False, fallback=True):
+        q = cls.query().filter(cls.api_key == api_key)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_api_key_%s" % api_key))
+        res = q.scalar()
+
+        if fallback and not res:
+            #fallback to additional keys
+            _res = UserApiKeys.query()\
+                .filter(UserApiKeys.api_key == api_key)\
+                .filter(or_(UserApiKeys.expires == -1,
+                            UserApiKeys.expires >= time.time()))\
+                .first()
+            if _res:
+                res = _res.user
+        return res
+
+    @classmethod
+    def get_by_email(cls, email, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.email.ilike(email))
+        else:
+            q = cls.query().filter(cls.email == email)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_email_key_%s" % email))
+
+        ret = q.scalar()
+        if ret is None:
+            q = UserEmailMap.query()
+            # try fetching in alternate email map
+            if case_insensitive:
+                q = q.filter(UserEmailMap.email.ilike(email))
+            else:
+                q = q.filter(UserEmailMap.email == email)
+            q = q.options(joinedload(UserEmailMap.user))
+            if cache:
+                q = q.options(FromCache("sql_cache_short",
+                                        "get_email_map_key_%s" % email))
+            ret = getattr(q.scalar(), 'user', None)
+
+        return ret
+
+    @classmethod
+    def get_from_cs_author(cls, author):
+        """
+        Tries to get User objects out of commit author string
+
+        :param author:
+        """
+        from rhodecode.lib.helpers import email, author_name
+        # Valid email in the attribute passed, see if they're in the system
+        _email = email(author)
+        if _email:
+            user = cls.get_by_email(_email, case_insensitive=True)
+            if user:
+                return user
+        # Maybe we can match by username?
+        _author = author_name(author)
+        user = cls.get_by_username(_author, case_insensitive=True)
+        if user:
+            return user
+
+    def update_lastlogin(self):
+        """Update user lastlogin"""
+        self.last_login = datetime.datetime.now()
+        Session().add(self)
+        log.debug('updated user %s lastlogin' % self.username)
+
+    @classmethod
+    def get_first_admin(cls):
+        user = User.query().filter(User.admin == True).first()
+        if user is None:
+            raise Exception('Missing administrative account!')
+        return user
+
+    @classmethod
+    def get_default_user(cls, cache=False):
+        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
+        if user is None:
+            raise Exception('Missing default account!')
+        return user
+
+    def get_api_data(self):
+        """
+        Common function for generating user related data for API
+        """
+        user = self
+        data = dict(
+            user_id=user.user_id,
+            username=user.username,
+            firstname=user.name,
+            lastname=user.lastname,
+            email=user.email,
+            emails=user.emails,
+            api_key=user.api_key,
+            api_keys=user.api_keys,
+            active=user.active,
+            admin=user.admin,
+            extern_type=user.extern_type,
+            extern_name=user.extern_name,
+            last_login=user.last_login,
+            ip_addresses=user.ip_addresses
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+            full_name=self.full_name,
+            full_name_or_username=self.full_name_or_username,
+            short_contact=self.short_contact,
+            full_contact=self.full_contact
+        )
+        data.update(self.get_api_data())
+        return data
+
+
+class UserApiKeys(Base, BaseModel):
+    __tablename__ = 'user_api_keys'
+    __table_args__ = (
+        Index('uak_api_key_idx', 'api_key'),
+        Index('uak_api_key_expires_idx', 'api_key', 'expires'),
+        UniqueConstraint('api_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True)
+    description = Column('description', UnicodeText(1024))
+    expires = Column('expires', Float(53), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    user = relationship('User', lazy='joined')
+
+    @property
+    def expired(self):
+        if self.expires == -1:
+            return False
+        return time.time() > self.expires
+
+
+class UserEmailMap(Base, BaseModel):
+    __tablename__ = 'user_email_map'
+    __table_args__ = (
+        Index('uem_email_idx', 'email'),
+        UniqueConstraint('email'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    user = relationship('User', lazy='joined')
+
+    @validates('_email')
+    def validate_email(self, key, email):
+        # check if this email is not main one
+        main_email = Session().query(User).filter(User.email == email).scalar()
+        if main_email is not None:
+            raise AttributeError('email %s is present is user table' % email)
+        return email
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+
+class UserIpMap(Base, BaseModel):
+    __tablename__ = 'user_ip_map'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'ip_addr'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    user = relationship('User', lazy='joined')
+
+    @classmethod
+    def _get_ip_range(cls, ip_addr):
+        from rhodecode.lib import ipaddr
+        net = ipaddr.IPNetwork(address=ip_addr)
+        return [str(net.network), str(net.broadcast)]
+
+    def __json__(self):
+        return dict(
+          ip_addr=self.ip_addr,
+          ip_range=self._get_ip_range(self.ip_addr)
+        )
+
+    def __unicode__(self):
+        return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
+                                            self.user_id, self.ip_addr)
+
+class UserLog(Base, BaseModel):
+    __tablename__ = 'user_logs'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None)
+    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.repository_name,
+                                      self.action)
+
+    @property
+    def action_as_day(self):
+        return datetime.date(*self.action_date.timetuple()[:3])
+
+    user = relationship('User')
+    repository = relationship('Repository', cascade='')
+
+
+class UserGroup(Base, BaseModel):
+    __tablename__ = 'users_groups'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    _group_data = Column("group_data", LargeBinary(), nullable=True)  # JSON data
+
+    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
+    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
+    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
+    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
+
+    user = relationship('User')
+
+    @hybrid_property
+    def group_data(self):
+        if not self._group_data:
+            return {}
+
+        try:
+            return json.loads(self._group_data)
+        except TypeError:
+            return {}
+
+    @group_data.setter
+    def group_data(self, val):
+        try:
+            self._group_data = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                      self.users_group_id,
+                                      self.users_group_name)
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False,
+                          case_insensitive=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.users_group_name.ilike(group_name))
+        else:
+            q = cls.query().filter(cls.users_group_name == group_name)
+        if cache:
+            q = q.options(FromCache(
+                            "sql_cache_short",
+                            "get_user_%s" % _hash_key(group_name)
+                          )
+            )
+        return q.scalar()
+
+    @classmethod
+    def get(cls, user_group_id, cache=False):
+        user_group = cls.query()
+        if cache:
+            user_group = user_group.options(FromCache("sql_cache_short",
+                                    "get_users_group_%s" % user_group_id))
+        return user_group.get(user_group_id)
+
+    def get_api_data(self, with_members=True):
+        user_group = self
+
+        data = dict(
+            users_group_id=user_group.users_group_id,
+            group_name=user_group.users_group_name,
+            group_description=user_group.user_group_description,
+            active=user_group.users_group_active,
+            owner=user_group.user.username,
+        )
+        if with_members:
+            members = []
+            for user in user_group.members:
+                user = user.user
+                members.append(user.get_api_data())
+            data['members'] = members
+
+        return data
+
+
+class UserGroupMember(Base, BaseModel):
+    __tablename__ = 'users_groups_members'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User', lazy='joined')
+    users_group = relationship('UserGroup')
+
+    def __init__(self, gr_id='', u_id=''):
+        self.users_group_id = gr_id
+        self.user_id = u_id
+
+
+class RepositoryField(Base, BaseModel):
+    __tablename__ = 'repositories_fields'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PREFIX = 'ex_'  # prefix used in form to not conflict with already existing fields
+
+    repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    field_key = Column("field_key", String(250, convert_unicode=False))
+    field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False)
+    field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False)
+    field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False)
+    field_type = Column("field_type", String(256), nullable=False, unique=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repository = relationship('Repository')
+
+    @property
+    def field_key_prefixed(self):
+        return 'ex_%s' % self.field_key
+
+    @classmethod
+    def un_prefix_key(cls, key):
+        if key.startswith(cls.PREFIX):
+            return key[len(cls.PREFIX):]
+        return key
+
+    @classmethod
+    def get_by_key_name(cls, key, repo):
+        row = cls.query()\
+                .filter(cls.repository == repo)\
+                .filter(cls.field_key == key).scalar()
+        return row
+
+
+class Repository(Base, BaseModel):
+    __tablename__ = 'repositories'
+    __table_args__ = (
+        UniqueConstraint('repo_name'),
+        Index('r_repo_name_idx', 'repo_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
+    DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
+
+    STATE_CREATED = 'repo_state_created'
+    STATE_PENDING = 'repo_state_pending'
+    STATE_ERROR = 'repo_state_error'
+
+    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    repo_state = Column("repo_state", String(255), nullable=True)
+
+    clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
+    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
+    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
+    description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    _landing_revision = Column("landing_revision", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    _locked = Column("locked", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
+
+    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
+
+    user = relationship('User')
+    fork = relationship('Repository', remote_side=repo_id)
+    group = relationship('RepoGroup')
+    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
+    stats = relationship('Statistics', cascade='all', uselist=False)
+
+    followers = relationship('UserFollowing',
+                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
+                             cascade='all')
+    extra_fields = relationship('RepositoryField',
+                                cascade="all, delete, delete-orphan")
+
+    logs = relationship('UserLog')
+    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
+
+    pull_requests_org = relationship('PullRequest',
+                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    pull_requests_other = relationship('PullRequest',
+                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
+                    cascade="all, delete, delete-orphan")
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
+                                   safe_unicode(self.repo_name))
+
+    @hybrid_property
+    def landing_rev(self):
+        # always should return [rev_type, rev]
+        if self._landing_revision:
+            _rev_info = self._landing_revision.split(':')
+            if len(_rev_info) < 2:
+                _rev_info.insert(0, 'rev')
+            return [_rev_info[0], _rev_info[1]]
+        return [None, None]
+
+    @landing_rev.setter
+    def landing_rev(self, val):
+        if ':' not in val:
+            raise ValueError('value must be delimited with `:` and consist '
+                             'of <rev_type>:<rev>, got %s instead' % val)
+        self._landing_revision = val
+
+    @hybrid_property
+    def locked(self):
+        # always should return [user_id, timelocked]
+        if self._locked:
+            _lock_info = self._locked.split(':')
+            return int(_lock_info[0]), _lock_info[1]
+        return [None, None]
+
+    @locked.setter
+    def locked(self, val):
+        if val and isinstance(val, (list, tuple)):
+            self._locked = ':'.join(map(str, val))
+        else:
+            self._locked = None
+
+    @hybrid_property
+    def changeset_cache(self):
+        from rhodecode.lib.vcs.backends.base import EmptyChangeset
+        dummy = EmptyChangeset().__json__()
+        if not self._changeset_cache:
+            return dummy
+        try:
+            return json.loads(self._changeset_cache)
+        except TypeError:
+            return dummy
+
+    @changeset_cache.setter
+    def changeset_cache(self, val):
+        try:
+            self._changeset_cache = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def normalize_repo_name(cls, repo_name):
+        """
+        Normalizes os specific repo_name to the format internally stored inside
+        dabatabase using URL_SEP
+
+        :param cls:
+        :param repo_name:
+        """
+        return cls.url_sep().join(repo_name.split(os.sep))
+
+    @classmethod
+    def get_by_repo_name(cls, repo_name):
+        q = Session().query(cls).filter(cls.repo_name == repo_name)
+        q = q.options(joinedload(Repository.fork))\
+                .options(joinedload(Repository.user))\
+                .options(joinedload(Repository.group))
+        return q.scalar()
+
+    @classmethod
+    def get_by_full_path(cls, repo_full_path):
+        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
+        repo_name = cls.normalize_repo_name(repo_name)
+        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
+
+    @classmethod
+    def get_repo_forks(cls, repo_id):
+        return cls.query().filter(Repository.fork_id == repo_id)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all repos are stored
+
+        :param cls:
+        """
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == cls.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def forks(self):
+        """
+        Return forks of this repo
+        """
+        return Repository.get_repo_forks(self.repo_id)
+
+    @property
+    def parent(self):
+        """
+        Returns fork parent
+        """
+        return self.fork
+
+    @property
+    def just_name(self):
+        return self.repo_name.split(Repository.url_sep())[-1]
+
+    @property
+    def groups_with_parents(self):
+        groups = []
+        if self.group is None:
+            return groups
+
+        cur_gr = self.group
+        groups.insert(0, cur_gr)
+        while 1:
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            groups.insert(0, gr)
+
+        return groups
+
+    @property
+    def groups_and_repo(self):
+        return self.groups_with_parents, self.just_name, self.repo_name
+
+    @LazyProperty
+    def repo_path(self):
+        """
+        Returns base full path for that repository means where it actually
+        exists on a filesystem
+        """
+        q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+                                              Repository.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def repo_full_path(self):
+        p = [self.repo_path]
+        # we need to split the name by / since this is how we store the
+        # names in the database, but that eventually needs to be converted
+        # into a valid system path
+        p += self.repo_name.split(Repository.url_sep())
+        return os.path.join(*map(safe_unicode, p))
+
+    @property
+    def cache_keys(self):
+        """
+        Returns associated cache keys for that repo
+        """
+        return CacheInvalidation.query()\
+            .filter(CacheInvalidation.cache_args == self.repo_name)\
+            .order_by(CacheInvalidation.cache_key)\
+            .all()
+
+    def get_new_name(self, repo_name):
+        """
+        returns new full repository name based on assigned group and new new
+
+        :param group_name:
+        """
+        path_prefix = self.group.full_path_splitted if self.group else []
+        return Repository.url_sep().join(path_prefix + [repo_name])
+
+    @property
+    def _ui(self):
+        """
+        Creates an db based ui object for this repository
+        """
+        from rhodecode.lib.utils import make_ui
+        return make_ui('db', clear_session=False)
+
+    @classmethod
+    def is_valid(cls, repo_name):
+        """
+        returns True if given repo name is a valid filesystem repository
+
+        :param cls:
+        :param repo_name:
+        """
+        from rhodecode.lib.utils import is_valid_repo
+
+        return is_valid_repo(repo_name, cls.base_path())
+
+    def get_api_data(self):
+        """
+        Common function for generating repo api data
+
+        """
+        repo = self
+        data = dict(
+            repo_id=repo.repo_id,
+            repo_name=repo.repo_name,
+            repo_type=repo.repo_type,
+            clone_uri=repo.clone_uri,
+            private=repo.private,
+            created_on=repo.created_on,
+            description=repo.description,
+            landing_rev=repo.landing_rev,
+            owner=repo.user.username,
+            fork_of=repo.fork.repo_name if repo.fork else None,
+            enable_statistics=repo.enable_statistics,
+            enable_locking=repo.enable_locking,
+            enable_downloads=repo.enable_downloads,
+            last_changeset=repo.changeset_cache,
+            locked_by=User.get(self.locked[0]).get_api_data() \
+                if self.locked[0] else None,
+            locked_date=time_to_datetime(self.locked[1]) \
+                if self.locked[1] else None
+        )
+        rc_config = RhodeCodeSetting.get_app_settings()
+        repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
+        if repository_fields:
+            for f in self.extra_fields:
+                data[f.field_key_prefixed] = f.field_value
+
+        return data
+
+    @classmethod
+    def lock(cls, repo, user_id, lock_time=None):
+        if not lock_time:
+            lock_time = time.time()
+        repo.locked = [user_id, lock_time]
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def unlock(cls, repo):
+        repo.locked = None
+        Session().add(repo)
+        Session().commit()
+
+    @classmethod
+    def getlock(cls, repo):
+        return repo.locked
+
+    @property
+    def last_db_change(self):
+        return self.updated_on
+
+    def clone_url(self, **override):
+        from pylons import url
+        qualified_home_url = url('home', qualified=True)
+
+        uri_tmpl = None
+        if 'with_id' in override:
+            uri_tmpl = self.DEFAULT_CLONE_URI_ID
+            del override['with_id']
+
+        if 'uri_tmpl' in override:
+            uri_tmpl = override['uri_tmpl']
+            del override['uri_tmpl']
+
+        # we didn't override our tmpl from **overrides
+        if not uri_tmpl:
+            uri_tmpl = self.DEFAULT_CLONE_URI
+            try:
+                from pylons import tmpl_context as c
+                uri_tmpl = c.clone_uri_tmpl
+            except Exception:
+                # in any case if we call this outside of request context,
+                # ie, not having tmpl_context set up
+                pass
+
+        return get_clone_url(uri_tmpl=uri_tmpl,
+                             qualifed_home_url=qualified_home_url,
+                             repo_name=self.repo_name,
+                             repo_id=self.repo_id, **override)
+
+    def set_state(self, state):
+        self.repo_state = state
+        Session().add(self)
+    #==========================================================================
+    # SCM PROPERTIES
+    #==========================================================================
+
+    def get_changeset(self, rev=None):
+        return get_changeset_safe(self.scm_instance, rev)
+
+    def get_landing_changeset(self):
+        """
+        Returns landing changeset, or if that doesn't exist returns the tip
+        """
+        _rev_type, _rev = self.landing_rev
+        cs = self.get_changeset(_rev)
+        if isinstance(cs, EmptyChangeset):
+            return self.get_changeset()
+        return cs
+
+    def update_changeset_cache(self, cs_cache=None):
+        """
+        Update cache of last changeset for repository, keys should be::
+
+            short_id
+            raw_id
+            revision
+            message
+            date
+            author
+
+        :param cs_cache:
+        """
+        from rhodecode.lib.vcs.backends.base import BaseChangeset
+        if cs_cache is None:
+            cs_cache = EmptyChangeset()
+            # use no-cache version here
+            scm_repo = self.scm_instance_no_cache()
+            if scm_repo:
+                cs_cache = scm_repo.get_changeset()
+
+        if isinstance(cs_cache, BaseChangeset):
+            cs_cache = cs_cache.__json__()
+
+        if (cs_cache != self.changeset_cache or not self.changeset_cache):
+            _default = datetime.datetime.fromtimestamp(0)
+            last_change = cs_cache.get('date') or _default
+            log.debug('updated repo %s with new cs cache %s'
+                      % (self.repo_name, cs_cache))
+            self.updated_on = last_change
+            self.changeset_cache = cs_cache
+            Session().add(self)
+            Session().commit()
+        else:
+            log.debug('Skipping repo:%s already with latest changes'
+                      % self.repo_name)
+
+    @property
+    def tip(self):
+        return self.get_changeset('tip')
+
+    @property
+    def author(self):
+        return self.tip.author
+
+    @property
+    def last_change(self):
+        return self.scm_instance.last_change
+
+    def get_comments(self, revisions=None):
+        """
+        Returns comments for this repository grouped by revisions
+
+        :param revisions: filter query by revisions only
+        """
+        cmts = ChangesetComment.query()\
+            .filter(ChangesetComment.repo == self)
+        if revisions:
+            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
+        grouped = collections.defaultdict(list)
+        for cmt in cmts.all():
+            grouped[cmt.revision].append(cmt)
+        return grouped
+
+    def statuses(self, revisions=None):
+        """
+        Returns statuses for this repository
+
+        :param revisions: list of revisions to get statuses for
+        """
+
+        statuses = ChangesetStatus.query()\
+            .filter(ChangesetStatus.repo == self)\
+            .filter(ChangesetStatus.version == 0)
+        if revisions:
+            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
+        grouped = {}
+
+        #maybe we have open new pullrequest without a status ?
+        stat = ChangesetStatus.STATUS_UNDER_REVIEW
+        status_lbl = ChangesetStatus.get_status_lbl(stat)
+        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
+            for rev in pr.revisions:
+                pr_id = pr.pull_request_id
+                pr_repo = pr.other_repo.repo_name
+                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
+
+        for stat in statuses.all():
+            pr_id = pr_repo = None
+            if stat.pull_request:
+                pr_id = stat.pull_request.pull_request_id
+                pr_repo = stat.pull_request.other_repo.repo_name
+            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
+                                      pr_id, pr_repo]
+        return grouped
+
+    def _repo_size(self):
+        from rhodecode.lib import helpers as h
+        log.debug('calculating repository size...')
+        return h.format_byte_size(self.scm_instance.size)
+
+    #==========================================================================
+    # SCM CACHE INSTANCE
+    #==========================================================================
+
+    def set_invalidate(self):
+        """
+        Mark caches of this repo as invalid.
+        """
+        CacheInvalidation.set_invalidate(self.repo_name)
+
+    def scm_instance_no_cache(self):
+        return self.__get_instance()
+
+    @property
+    def scm_instance(self):
+        import rhodecode
+        full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
+        if full_cache:
+            return self.scm_instance_cached()
+        return self.__get_instance()
+
+    def scm_instance_cached(self, valid_cache_keys=None):
+        @cache_region('long_term')
+        def _c(repo_name):
+            return self.__get_instance()
+        rn = self.repo_name
+
+        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
+        if not valid:
+            log.debug('Cache for %s invalidated, getting new object' % (rn))
+            region_invalidate(_c, None, rn)
+        else:
+            log.debug('Getting obj for %s from cache' % (rn))
+        return _c(rn)
+
+    def __get_instance(self):
+        repo_full_path = self.repo_full_path
+        try:
+            alias = get_scm(repo_full_path)[0]
+            log.debug('Creating instance of %s repository from %s'
+                      % (alias, repo_full_path))
+            backend = get_backend(alias)
+        except VCSError:
+            log.error(traceback.format_exc())
+            log.error('Perhaps this repository is in db and not in '
+                      'filesystem run rescan repositories with '
+                      '"destroy old data " option from admin panel')
+            return
+
+        if alias == 'hg':
+
+            repo = backend(safe_str(repo_full_path), create=False,
+                           baseui=self._ui)
+            # skip hidden web repository
+            if repo._get_hidden():
+                return
+        else:
+            repo = backend(repo_full_path, create=False)
+
+        return repo
+
+    def __json__(self):
+        return dict(landing_rev = self.landing_rev)
+
+class RepoGroup(Base, BaseModel):
+    __tablename__ = 'groups'
+    __table_args__ = (
+        UniqueConstraint('group_name', 'group_parent_id'),
+        CheckConstraint('group_id != group_parent_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    __mapper_args__ = {'order_by': 'group_name'}
+
+    SEP = ' &raquo; '
+
+    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
+    group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
+    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
+    parent_group = relationship('RepoGroup', remote_side=group_id)
+    user = relationship('User')
+
+    def __init__(self, group_name='', parent_group=None):
+        self.group_name = group_name
+        self.parent_group = parent_group
+
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
+                                      self.group_name)
+
+    @classmethod
+    def _generate_choice(cls, repo_group):
+        from webhelpers.html import literal as _literal
+        _name = lambda k: _literal(cls.SEP.join(k))
+        return repo_group.group_id, _name(repo_group.full_path_splitted)
+
+    @classmethod
+    def groups_choices(cls, groups=None, show_empty_group=True):
+        if not groups:
+            groups = cls.query().all()
+
+        repo_groups = []
+        if show_empty_group:
+            repo_groups = [('-1', u'-- %s --' % _('top level'))]
+
+        repo_groups.extend([cls._generate_choice(x) for x in groups])
+
+        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0])
+        return repo_groups
+
+    @classmethod
+    def url_sep(cls):
+        return URL_SEP
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+        if case_insensitive:
+            gr = cls.query()\
+                .filter(cls.group_name.ilike(group_name))
+        else:
+            gr = cls.query()\
+                .filter(cls.group_name == group_name)
+        if cache:
+            gr = gr.options(FromCache(
+                            "sql_cache_short",
+                            "get_group_%s" % _hash_key(group_name)
+                            )
+            )
+        return gr.scalar()
+
+    @property
+    def parents(self):
+        parents_recursion_limit = 5
+        groups = []
+        if self.parent_group is None:
+            return groups
+        cur_gr = self.parent_group
+        groups.insert(0, cur_gr)
+        cnt = 0
+        while 1:
+            cnt += 1
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            if cnt == parents_recursion_limit:
+                # this will prevent accidental infinit loops
+                log.error('group nested more than %s' %
+                          parents_recursion_limit)
+                break
+
+            groups.insert(0, gr)
+        return groups
+
+    @property
+    def children(self):
+        return RepoGroup.query().filter(RepoGroup.parent_group == self)
+
+    @property
+    def name(self):
+        return self.group_name.split(RepoGroup.url_sep())[-1]
+
+    @property
+    def full_path(self):
+        return self.group_name
+
+    @property
+    def full_path_splitted(self):
+        return self.group_name.split(RepoGroup.url_sep())
+
+    @property
+    def repositories(self):
+        return Repository.query()\
+                .filter(Repository.group == self)\
+                .order_by(Repository.repo_name)
+
+    @property
+    def repositories_recursive_count(self):
+        cnt = self.repositories.count()
+
+        def children_count(group):
+            cnt = 0
+            for child in group.children:
+                cnt += child.repositories.count()
+                cnt += children_count(child)
+            return cnt
+
+        return cnt + children_count(self)
+
+    def _recursive_objects(self, include_repos=True):
+        all_ = []
+
+        def _get_members(root_gr):
+            if include_repos:
+                for r in root_gr.repositories:
+                    all_.append(r)
+            childs = root_gr.children.all()
+            if childs:
+                for gr in childs:
+                    all_.append(gr)
+                    _get_members(gr)
+
+        _get_members(self)
+        return [self] + all_
+
+    def recursive_groups_and_repos(self):
+        """
+        Recursive return all groups, with repositories in those groups
+        """
+        return self._recursive_objects()
+
+    def recursive_groups(self):
+        """
+        Returns all children groups for this group including children of children
+        """
+        return self._recursive_objects(include_repos=False)
+
+    def get_new_name(self, group_name):
+        """
+        returns new full group name based on parent and new name
+
+        :param group_name:
+        """
+        path_prefix = (self.parent_group.full_path_splitted if
+                       self.parent_group else [])
+        return RepoGroup.url_sep().join(path_prefix + [group_name])
+
+    def get_api_data(self):
+        """
+        Common function for generating api data
+
+        """
+        group = self
+        data = dict(
+            group_id=group.group_id,
+            group_name=group.group_name,
+            group_description=group.group_description,
+            parent_group=group.parent_group.group_name if group.parent_group else None,
+            repositories=[x.repo_name for x in group.repositories],
+            owner=group.user.username
+        )
+        return data
+
+
+class Permission(Base, BaseModel):
+    __tablename__ = 'permissions'
+    __table_args__ = (
+        Index('p_perm_name_idx', 'permission_name'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    PERMS = [
+        ('hg.admin', _('RhodeCode Administrator')),
+
+        ('repository.none', _('Repository no access')),
+        ('repository.read', _('Repository read access')),
+        ('repository.write', _('Repository write access')),
+        ('repository.admin', _('Repository admin access')),
+
+        ('group.none', _('Repository group no access')),
+        ('group.read', _('Repository group read access')),
+        ('group.write', _('Repository group write access')),
+        ('group.admin', _('Repository group admin access')),
+
+        ('usergroup.none', _('User group no access')),
+        ('usergroup.read', _('User group read access')),
+        ('usergroup.write', _('User group write access')),
+        ('usergroup.admin', _('User group admin access')),
+
+        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
+        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
+
+        ('hg.usergroup.create.false', _('User Group creation disabled')),
+        ('hg.usergroup.create.true', _('User Group creation enabled')),
+
+        ('hg.create.none', _('Repository creation disabled')),
+        ('hg.create.repository', _('Repository creation enabled')),
+        ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
+        ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
+
+        ('hg.fork.none', _('Repository forking disabled')),
+        ('hg.fork.repository', _('Repository forking enabled')),
+
+        ('hg.register.none', _('Registration disabled')),
+        ('hg.register.manual_activate', _('User Registration with manual account activation')),
+        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
+
+        ('hg.extern_activate.manual', _('Manual activation of external account')),
+        ('hg.extern_activate.auto', _('Automatic activation of external account')),
+
+    ]
+
+    #definition of system default permissions for DEFAULT user
+    DEFAULT_USER_PERMISSIONS = [
+        'repository.read',
+        'group.read',
+        'usergroup.read',
+        'hg.create.repository',
+        'hg.create.write_on_repogroup.true',
+        'hg.fork.repository',
+        'hg.register.manual_activate',
+        'hg.extern_activate.auto',
+    ]
+
+    # defines which permissions are more important higher the more important
+    # Weight defines which permissions are more important.
+    # The higher number the more important.
+    PERM_WEIGHTS = {
+        'repository.none': 0,
+        'repository.read': 1,
+        'repository.write': 3,
+        'repository.admin': 4,
+
+        'group.none': 0,
+        'group.read': 1,
+        'group.write': 3,
+        'group.admin': 4,
+
+        'usergroup.none': 0,
+        'usergroup.read': 1,
+        'usergroup.write': 3,
+        'usergroup.admin': 4,
+        'hg.repogroup.create.false': 0,
+        'hg.repogroup.create.true': 1,
+
+        'hg.usergroup.create.false': 0,
+        'hg.usergroup.create.true': 1,
+
+        'hg.fork.none': 0,
+        'hg.fork.repository': 1,
+        'hg.create.none': 0,
+        'hg.create.repository': 1
+    }
+
+    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__, self.permission_id, self.permission_name
+        )
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.permission_name == key).scalar()
+
+    @classmethod
+    def get_default_perms(cls, default_user_id):
+        q = Session().query(UserRepoToPerm, Repository, cls)\
+         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
+         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_group_perms(cls, default_user_id):
+        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
+         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
+         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_user_group_perms(cls, default_user_id):
+        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
+         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
+         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserUserGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+
+class UserRepoToPerm(Base, BaseModel):
+    __tablename__ = 'repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    repository = relationship('Repository')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, repository, permission):
+        n = cls()
+        n.user = user
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.repository)
+
+
+class UserUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    user_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, user_group, permission):
+        n = cls()
+        n.user = user
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.user_group)
+
+
+class UserToPerm(Base, BaseModel):
+    __tablename__ = 'user_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    permission = relationship('Permission', lazy='joined')
+
+    def __unicode__(self):
+        return u'<%s => %s >' % (self.user, self.permission)
+
+
+class UserGroupRepoToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_to_perm'
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    repository = relationship('Repository')
+
+    @classmethod
+    def create(cls, users_group, repository, permission):
+        n = cls()
+        n.users_group = users_group
+        n.repository = repository
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
+
+
+class UserGroupUserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_group_user_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
+        CheckConstraint('target_user_group_id != user_group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
+    user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, target_user_group, user_group, permission):
+        n = cls()
+        n.target_user_group = target_user_group
+        n.user_group = user_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+    def __unicode__(self):
+        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
+
+
+class UserGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'permission_id',),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+
+
+class UserRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'group_id', 'permission_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    group = relationship('RepoGroup')
+    permission = relationship('Permission')
+
+    @classmethod
+    def create(cls, user, repository_group, permission):
+        n = cls()
+        n.user = user
+        n.group = repository_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+
+class UserGroupRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'group_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UserGroup')
+    permission = relationship('Permission')
+    group = relationship('RepoGroup')
+
+    @classmethod
+    def create(cls, user_group, repository_group, permission):
+        n = cls()
+        n.users_group = user_group
+        n.group = repository_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
+
+class Statistics(Base, BaseModel):
+    __tablename__ = 'statistics'
+    __table_args__ = (
+         UniqueConstraint('repository_id'),
+         {'extend_existing': True, 'mysql_engine': 'InnoDB',
+          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
+    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
+    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
+    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
+
+    repository = relationship('Repository', single_parent=True)
+
+
+class UserFollowing(Base, BaseModel):
+    __tablename__ = 'user_followings'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'follows_repository_id'),
+        UniqueConstraint('user_id', 'follows_user_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+
+    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
+    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+
+    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
+
+    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
+    follows_repository = relationship('Repository', order_by='Repository.repo_name')
+
+    @classmethod
+    def get_repo_followers(cls, repo_id):
+        return cls.query().filter(cls.follows_repo_id == repo_id)
+
+
+class CacheInvalidation(Base, BaseModel):
+    __tablename__ = 'cache_invalidation'
+    __table_args__ = (
+        UniqueConstraint('cache_key'),
+        Index('key_idx', 'cache_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    # cache_id, not used
+    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    # cache_key as created by _get_cache_key
+    cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    # cache_args is a repo_name
+    cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    # instance sets cache_active True when it is caching,
+    # other instances set cache_active to False to indicate that this cache is invalid
+    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+
+    def __init__(self, cache_key, repo_name=''):
+        self.cache_key = cache_key
+        self.cache_args = repo_name
+        self.cache_active = False
+
+    def __unicode__(self):
+        return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
+                            self.cache_id, self.cache_key, self.cache_active)
+
+    def _cache_key_partition(self):
+        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
+        return prefix, repo_name, suffix
+
+    def get_prefix(self):
+        """
+        get prefix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[0]
+
+    def get_suffix(self):
+        """
+        get suffix that might have been used in _get_cache_key to
+        generate self.cache_key. Only used for informational purposes
+        in repo_edit.html.
+        """
+        # prefix, repo_name, suffix
+        return self._cache_key_partition()[2]
+
+    @classmethod
+    def clear_cache(cls):
+        """
+        Delete all cache keys from database.
+        Should only be run when all instances are down and all entries thus stale.
+        """
+        cls.query().delete()
+        Session().commit()
+
+    @classmethod
+    def _get_cache_key(cls, key):
+        """
+        Wrapper for generating a unique cache key for this instance and "key".
+        key must / will start with a repo_name which will be stored in .cache_args .
+        """
+        import rhodecode
+        prefix = rhodecode.CONFIG.get('instance_id', '')
+        return "%s%s" % (prefix, key)
+
+    @classmethod
+    def set_invalidate(cls, repo_name, delete=False):
+        """
+        Mark all caches of a repo as invalid in the database.
+        """
+        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
+        log.debug('for repo %s got %s invalidation objects'
+                  % (safe_str(repo_name), inv_objs))
+        try:
+            for inv_obj in inv_objs:
+                log.debug('marking %s key for invalidation based on repo_name=%s'
+                          % (inv_obj, safe_str(repo_name)))
+                if delete:
+                    Session().delete(inv_obj)
+                else:
+                    inv_obj.cache_active = False
+                    Session().add(inv_obj)
+            Session().commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+
+    @classmethod
+    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
+        """
+        Mark this cache key as active and currently cached.
+        Return True if the existing cache registration still was valid.
+        Return False to indicate that it had been invalidated and caches should be refreshed.
+        """
+
+        key = (repo_name + '_' + kind) if kind else repo_name
+        cache_key = cls._get_cache_key(key)
+
+        if valid_cache_keys and cache_key in valid_cache_keys:
+            return True
+
+        try:
+            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
+            if not inv_obj:
+                inv_obj = CacheInvalidation(cache_key, repo_name)
+            was_valid = inv_obj.cache_active
+            inv_obj.cache_active = True
+            Session().add(inv_obj)
+            Session().commit()
+            return was_valid
+        except Exception:
+            log.error(traceback.format_exc())
+            Session().rollback()
+            return False
+
+    @classmethod
+    def get_valid_cache_keys(cls):
+        """
+        Return opaque object with information of which caches still are valid
+        and can be used without checking for invalidation.
+        """
+        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
+
+
+class ChangesetComment(Base, BaseModel):
+    __tablename__ = 'changeset_comments'
+    __table_args__ = (
+        Index('cc_revision_idx', 'revision'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    revision = Column('revision', String(40), nullable=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+    line_no = Column('line_no', Unicode(10), nullable=True)
+    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
+    f_path = Column('f_path', Unicode(1000), nullable=True)
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+    text = Column('text', UnicodeText(25000), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    @classmethod
+    def get_users(cls, revision=None, pull_request_id=None):
+        """
+        Returns user associated with this ChangesetComment. ie those
+        who actually commented
+
+        :param cls:
+        :param revision:
+        """
+        q = Session().query(User)\
+                .join(ChangesetComment.author)
+        if revision:
+            q = q.filter(cls.revision == revision)
+        elif pull_request_id:
+            q = q.filter(cls.pull_request_id == pull_request_id)
+        return q.all()
+
+
+class ChangesetStatus(Base, BaseModel):
+    __tablename__ = 'changeset_statuses'
+    __table_args__ = (
+        Index('cs_revision_idx', 'revision'),
+        Index('cs_version_idx', 'version'),
+        UniqueConstraint('repo_id', 'revision', 'version'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
+    STATUS_APPROVED = 'approved'
+    STATUS_REJECTED = 'rejected'
+    STATUS_UNDER_REVIEW = 'under_review'
+
+    STATUSES = [
+        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
+        (STATUS_APPROVED, _("Approved")),
+        (STATUS_REJECTED, _("Rejected")),
+        (STATUS_UNDER_REVIEW, _("Under Review")),
+    ]
+
+    changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    revision = Column('revision', String(40), nullable=False)
+    status = Column('status', String(128), nullable=False, default=DEFAULT)
+    changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
+    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
+    version = Column('version', Integer(), nullable=False, default=0)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+    comment = relationship('ChangesetComment', lazy='joined')
+    pull_request = relationship('PullRequest', lazy='joined')
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__,
+            self.status, self.author
+        )
+
+    @classmethod
+    def get_status_lbl(cls, value):
+        return dict(cls.STATUSES).get(value)
+
+    @property
+    def status_lbl(self):
+        return ChangesetStatus.get_status_lbl(self.status)
+
+
+class PullRequest(Base, BaseModel):
+    __tablename__ = 'pull_requests'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    # values for .status
+    STATUS_NEW = u'new'
+    STATUS_OPEN = u'open'
+    STATUS_CLOSED = u'closed'
+
+    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
+    title = Column('title', Unicode(256), nullable=True)
+    description = Column('description', UnicodeText(10240), nullable=True)
+    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    _revisions = Column('revisions', UnicodeText(20500))  # 500 revisions max
+    org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    org_ref = Column('org_ref', Unicode(256), nullable=False)
+    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    other_ref = Column('other_ref', Unicode(256), nullable=False)
+
+    @hybrid_property
+    def revisions(self):
+        return self._revisions.split(':')
+
+    @revisions.setter
+    def revisions(self, val):
+        self._revisions = ':'.join(val)
+
+    @property
+    def org_ref_parts(self):
+        return self.org_ref.split(':')
+
+    @property
+    def other_ref_parts(self):
+        return self.other_ref.split(':')
+
+    author = relationship('User', lazy='joined')
+    reviewers = relationship('PullRequestReviewers',
+                             cascade="all, delete, delete-orphan")
+    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
+    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
+    statuses = relationship('ChangesetStatus')
+    comments = relationship('ChangesetComment',
+                             cascade="all, delete, delete-orphan")
+
+    def is_closed(self):
+        return self.status == self.STATUS_CLOSED
+
+    @property
+    def last_review_status(self):
+        return self.statuses[-1].status if self.statuses else ''
+
+    def __json__(self):
+        return dict(
+            revisions=self.revisions
+        )
+
+
+class PullRequestReviewers(Base, BaseModel):
+    __tablename__ = 'pull_request_reviewers'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    def __init__(self, user=None, pull_request=None):
+        self.user = user
+        self.pull_request = pull_request
+
+    pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
+
+    user = relationship('User')
+    pull_request = relationship('PullRequest')
+
+
+class Notification(Base, BaseModel):
+    __tablename__ = 'notifications'
+    __table_args__ = (
+        Index('notification_type_idx', 'type'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+
+    TYPE_CHANGESET_COMMENT = u'cs_comment'
+    TYPE_MESSAGE = u'message'
+    TYPE_MENTION = u'mention'
+    TYPE_REGISTRATION = u'registration'
+    TYPE_PULL_REQUEST = u'pull_request'
+    TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
+
+    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
+    subject = Column('subject', Unicode(512), nullable=True)
+    body = Column('body', UnicodeText(50000), nullable=True)
+    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    type_ = Column('type', Unicode(256))
+
+    created_by_user = relationship('User')
+    notifications_to_users = relationship('UserNotification', lazy='joined',
+                                          cascade="all, delete, delete-orphan")
+
+    @property
+    def recipients(self):
+        return [x.user for x in UserNotification.query()\
+                .filter(UserNotification.notification == self)\
+                .order_by(UserNotification.user_id.asc()).all()]
+
+    @classmethod
+    def create(cls, created_by, subject, body, recipients, type_=None):
+        if type_ is None:
+            type_ = Notification.TYPE_MESSAGE
+
+        notification = cls()
+        notification.created_by_user = created_by
+        notification.subject = subject
+        notification.body = body
+        notification.type_ = type_
+        notification.created_on = datetime.datetime.now()
+
+        for u in recipients:
+            assoc = UserNotification()
+            assoc.notification = notification
+            u.notifications.append(assoc)
+        Session().add(notification)
+        return notification
+
+    @property
+    def description(self):
+        from rhodecode.model.notification import NotificationModel
+        return NotificationModel().make_description(self)
+
+
+class UserNotification(Base, BaseModel):
+    __tablename__ = 'user_to_notification'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'notification_id'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
+    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
+    read = Column('read', Boolean, default=False)
+    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
+
+    user = relationship('User', lazy="joined")
+    notification = relationship('Notification', lazy="joined",
+                                order_by=lambda: Notification.created_on.desc(),)
+
+    def mark_as_read(self):
+        self.read = True
+        Session().add(self)
+
+
+class Gist(Base, BaseModel):
+    __tablename__ = 'gists'
+    __table_args__ = (
+        Index('g_gist_access_id_idx', 'gist_access_id'),
+        Index('g_created_on_idx', 'created_on'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    GIST_PUBLIC = u'public'
+    GIST_PRIVATE = u'private'
+    DEFAULT_FILENAME = u'gistfile1.txt'
+
+    gist_id = Column('gist_id', Integer(), primary_key=True)
+    gist_access_id = Column('gist_access_id', Unicode(250))
+    gist_description = Column('gist_description', UnicodeText(1024))
+    gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
+    gist_expires = Column('gist_expires', Float(53), nullable=False)
+    gist_type = Column('gist_type', Unicode(128), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    owner = relationship('User')
+
+    def __repr__(self):
+        return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
+
+    @classmethod
+    def get_or_404(cls, id_):
+        res = cls.query().filter(cls.gist_access_id == id_).scalar()
+        if not res:
+            raise HTTPNotFound
+        return res
+
+    @classmethod
+    def get_by_access_id(cls, gist_access_id):
+        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
+
+    def gist_url(self):
+        import rhodecode
+        alias_url = rhodecode.CONFIG.get('gist_alias_url')
+        if alias_url:
+            return alias_url.replace('{gistid}', self.gist_access_id)
+
+        from pylons import url
+        return url('gist', gist_id=self.gist_access_id, qualified=True)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all gists are stored
+
+        :param cls:
+        """
+        from rhodecode.model.gist import GIST_STORE_LOC
+        q = Session().query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == URL_SEP)
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
+
+    def get_api_data(self):
+        """
+        Common function for generating gist related data for API
+        """
+        gist = self
+        data = dict(
+            gist_id=gist.gist_id,
+            type=gist.gist_type,
+            access_id=gist.gist_access_id,
+            description=gist.gist_description,
+            url=gist.gist_url(),
+            expires=gist.gist_expires,
+            created_on=gist.created_on,
+        )
+        return data
+
+    def __json__(self):
+        data = dict(
+        )
+        data.update(self.get_api_data())
+        return data
+    ## SCM functions
+
+    @property
+    def scm_instance(self):
+        from rhodecode.lib.vcs import get_repo
+        base_path = self.base_path()
+        return get_repo(os.path.join(*map(safe_str,
+                                          [base_path, self.gist_access_id])))
+
+
+class DbMigrateVersion(Base, BaseModel):
+    __tablename__ = 'db_migrate_version'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+    )
+    repository_id = Column('repository_id', String(250), primary_key=True)
+    repository_path = Column('repository_path', Text)
+    version = Column('version', Integer)
--- a/rhodecode/lib/dbmigrate/versions/008_version_1_5_0.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/versions/008_version_1_5_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -22,23 +22,24 @@
     Upgrade operations go here.
     Don't create your own engine; bind migrate_engine to your metadata
     """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_1_5_0
     #==========================================================================
     # USER LOGS
     #==========================================================================
-    _reset_base(migrate_engine)
-    from rhodecode.lib.dbmigrate.schema.db_1_5_0 import UserLog
-    tbl = UserLog.__table__
+
+    tbl = db_1_5_0.UserLog.__table__
     username = Column("username", String(255, convert_unicode=False,
                                          assert_unicode=None), nullable=True,
                       unique=None, default=None)
     # create username column
     username.create(table=tbl)
 
-    _Session = Session()
+    _Session = meta.Session()
     ## after adding that column fix all usernames
-    users_log = _Session.query(UserLog)\
-            .options(joinedload(UserLog.user))\
-            .options(joinedload(UserLog.repository)).all()
+    users_log = _Session.query(db_1_5_0.UserLog)\
+            .options(joinedload(db_1_5_0.UserLog.user))\
+            .options(joinedload(db_1_5_0.UserLog.repository)).all()
 
     for entry in users_log:
         entry.username = entry.user.username
@@ -46,8 +47,7 @@
     _Session.commit()
 
     #alter username to not null
-    from rhodecode.lib.dbmigrate.schema.db_1_5_0 import UserLog
-    tbl_name = UserLog.__tablename__
+    tbl_name = db_1_5_0.UserLog.__tablename__
     tbl = Table(tbl_name,
                 MetaData(bind=migrate_engine), autoload=True,
                 autoload_with=migrate_engine)
@@ -56,7 +56,73 @@
     # remove nullability from revision field
     col.alter(nullable=False)
 
+    # issue fixups
+    fixups(db_1_5_0, meta.Session)
+
 
 def downgrade(migrate_engine):
     meta = MetaData()
     meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    # ** create default permissions ** #
+    #=====================================
+    for p in models.Permission.PERMS:
+        if not models.Permission.get_by_key(p[0]):
+            new_perm = models.Permission()
+            new_perm.permission_name = p[0]
+            new_perm.permission_longname = p[0]  #translation err with p[1]
+            print 'Creating new permission %s' % p[0]
+            _SESSION().add(new_perm)
+
+    _SESSION().commit()
+
+    # ** populate default permissions ** #
+    #=====================================
+
+    user = models.User.query().filter(models.User.username == 'default').scalar()
+
+    def _make_perm(perm):
+        new_perm = models.UserToPerm()
+        new_perm.user = user
+        new_perm.permission = models.Permission.get_by_key(perm)
+        return new_perm
+
+    def _get_group(perm_name):
+        return '.'.join(perm_name.split('.')[:1])
+
+    perms = models.UserToPerm.query().filter(models.UserToPerm.user == user).all()
+    defined_perms_groups = map(_get_group,
+                              (x.permission.permission_name for x in perms))
+    log.debug('GOT ALREADY DEFINED:%s' % perms)
+    DEFAULT_PERMS = models.Permission.DEFAULT_USER_PERMISSIONS
+
+    # for every default permission that needs to be created, we check if
+    # it's group is already defined, if it's not we create default perm
+    for perm_name in DEFAULT_PERMS:
+        gr = _get_group(perm_name)
+        if gr not in defined_perms_groups:
+            log.debug('GR:%s not found, creating permission %s'
+                      % (gr, perm_name))
+            new_perm = _make_perm(perm_name)
+            _SESSION().add(new_perm)
+    _SESSION().commit()
+
+    # ** create default options ** #
+    #===============================
+    skip_existing = True
+    for k, v in [
+        ('default_repo_enable_locking',  False),
+        ('default_repo_enable_downloads', False),
+        ('default_repo_enable_statistics', False),
+        ('default_repo_private', False),
+        ('default_repo_type', 'hg')]:
+
+        if skip_existing and models.RhodeCodeSetting.get_by_name(k) is not None:
+            log.debug('Skipping option %s' % k)
+            continue
+        setting = models.RhodeCodeSetting(k, v)
+        _SESSION().add(setting)
+
+    _SESSION().commit()
--- a/rhodecode/lib/dbmigrate/versions/010_version_1_5_2.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/versions/010_version_1_5_2.py	Wed Jul 02 19:03:13 2014 -0400
@@ -12,7 +12,7 @@
 
 from rhodecode.model.meta import Base
 from rhodecode.model import meta
-from rhodecode.lib.dbmigrate.versions import _reset_base
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
 
 log = logging.getLogger(__name__)
 
@@ -23,28 +23,34 @@
     Don't create your own engine; bind migrate_engine to your metadata
     """
     _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_1_5_2
     #==========================================================================
     # USER LOGS
     #==========================================================================
-    from rhodecode.lib.dbmigrate.schema.db_1_5_2 import UserIpMap
-    tbl = UserIpMap.__table__
+    tbl = db_1_5_2.UserIpMap.__table__
     tbl.create()
 
     #==========================================================================
     # REPOSITORIES
     #==========================================================================
-    from rhodecode.lib.dbmigrate.schema.db_1_5_2 import Repository
-    tbl = Repository.__table__
+    tbl = db_1_5_2.Repository.__table__
     changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True)
     # create username column
     changeset_cache.create(table=tbl)
 
-    #fix cache data
-    repositories = Repository.getAll()
-    for entry in repositories:
-        entry.update_changeset_cache()
+    # issue fixups
+    fixups(db_1_5_2, meta.Session)
 
 
 def downgrade(migrate_engine):
     meta = MetaData()
     meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    notify('Upgrading repositories Caches')
+    repositories = models.Repository.getAll()
+    for repo in repositories:
+        print repo
+        repo.update_changeset_cache()
+        _SESSION().commit()
--- a/rhodecode/lib/dbmigrate/versions/011_version_1_6_0.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/versions/011_version_1_6_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -12,7 +12,7 @@
 
 from rhodecode.model.meta import Base
 from rhodecode.model import meta
-from rhodecode.lib.dbmigrate.versions import _reset_base
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
 
 log = logging.getLogger(__name__)
 
@@ -23,14 +23,27 @@
     Don't create your own engine; bind migrate_engine to your metadata
     """
     _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_1_6_0
+
     #==========================================================================
     # USER LOGS
     #==========================================================================
-    from rhodecode.lib.dbmigrate.schema.db_1_6_0 import RepositoryField
-    tbl = RepositoryField.__table__
+    tbl = db_1_6_0.RepositoryField.__table__
     tbl.create()
 
+    # issue fixups
+    fixups(db_1_6_0, meta.Session)
+
 
 def downgrade(migrate_engine):
     meta = MetaData()
     meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    notify('Upgrading repositories Caches')
+    repositories = models.Repository.getAll()
+    for repo in repositories:
+        print repo
+        repo.update_changeset_cache()
+        _SESSION().commit()
--- a/rhodecode/lib/dbmigrate/versions/012_version_1_7_0.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/versions/012_version_1_7_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -23,46 +23,128 @@
     Don't create your own engine; bind migrate_engine to your metadata
     """
     _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_1_7_0
 
     #==========================================================================
     # UserUserGroupToPerm
     #==========================================================================
-    from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserUserGroupToPerm
-    tbl = UserUserGroupToPerm.__table__
+    tbl = db_1_7_0.UserUserGroupToPerm.__table__
     tbl.create()
 
     #==========================================================================
     # UserGroupUserGroupToPerm
     #==========================================================================
-    from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserGroupUserGroupToPerm
-    tbl = UserGroupUserGroupToPerm.__table__
+    tbl = db_1_7_0.UserGroupUserGroupToPerm.__table__
     tbl.create()
 
     #==========================================================================
     # Gist
     #==========================================================================
-    from rhodecode.lib.dbmigrate.schema.db_1_7_0 import Gist
-    tbl = Gist.__table__
+    tbl = db_1_7_0.Gist.__table__
     tbl.create()
 
     #==========================================================================
     # UserGroup
     #==========================================================================
-    from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserGroup
-    tbl = UserGroup.__table__
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=False, default=None)
+    tbl = db_1_7_0.UserGroup.__table__
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
+                     nullable=True, unique=False, default=None)
     # create username column
     user_id.create(table=tbl)
 
     #==========================================================================
     # RepoGroup
     #==========================================================================
-    from rhodecode.lib.dbmigrate.schema.db_1_7_0 import RepoGroup
-    tbl = RepoGroup.__table__
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=False, default=None)
+    tbl = db_1_7_0.RepoGroup.__table__
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
+                     nullable=True, unique=False, default=None)
     # create username column
     user_id.create(table=tbl)
 
+    # issue fixups
+    fixups(db_1_7_0, meta.Session)
+
+
 def downgrade(migrate_engine):
     meta = MetaData()
     meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    # ** create default permissions ** #
+    #=====================================
+    for p in models.Permission.PERMS:
+        if not models.Permission.get_by_key(p[0]):
+            new_perm = models.Permission()
+            new_perm.permission_name = p[0]
+            new_perm.permission_longname = p[0]  #translation err with p[1]
+            _SESSION().add(new_perm)
+
+    _SESSION().commit()
+
+    # ** populate default permissions ** #
+    #=====================================
+
+    user = models.User.query().filter(models.User.username == 'default').scalar()
+
+    def _make_perm(perm):
+        new_perm = models.UserToPerm()
+        new_perm.user = user
+        new_perm.permission = models.Permission.get_by_key(perm)
+        return new_perm
+
+    def _get_group(perm_name):
+        return '.'.join(perm_name.split('.')[:1])
+
+    perms = models.UserToPerm.query().filter(models.UserToPerm.user == user).all()
+    defined_perms_groups = map(_get_group,
+                              (x.permission.permission_name for x in perms))
+    log.debug('GOT ALREADY DEFINED:%s' % perms)
+    DEFAULT_PERMS = models.Permission.DEFAULT_USER_PERMISSIONS
+
+    # for every default permission that needs to be created, we check if
+    # it's group is already defined, if it's not we create default perm
+    for perm_name in DEFAULT_PERMS:
+        gr = _get_group(perm_name)
+        if gr not in defined_perms_groups:
+            log.debug('GR:%s not found, creating permission %s'
+                      % (gr, perm_name))
+            new_perm = _make_perm(perm_name)
+            _SESSION().add(new_perm)
+    _SESSION().commit()
+
+    #fix all usergroups
+
+    def _create_default_perms(user_group):
+        # create default permission
+        default_perm = 'usergroup.read'
+        def_user = models.User.get_default_user()
+        for p in def_user.user_perms:
+            if p.permission.permission_name.startswith('usergroup.'):
+                default_perm = p.permission.permission_name
+                break
+
+        user_group_to_perm = models.UserUserGroupToPerm()
+        user_group_to_perm.permission = models.Permission.get_by_key(default_perm)
+
+        user_group_to_perm.user_group = user_group
+        user_group_to_perm.user_id = def_user.user_id
+        return user_group_to_perm
+
+    for ug in models.UserGroup.get_all():
+        perm_obj = _create_default_perms(ug)
+        _SESSION().add(perm_obj)
+    _SESSION().commit()
+
+    adm = models.User.get_first_admin()
+    # fix owners of UserGroup
+    for ug in _SESSION().query(models.UserGroup).all():
+        ug.user_id = adm.user_id
+        _SESSION().add(ug)
+    _SESSION().commit()
+
+    # fix owners of RepoGroup
+    for ug in _SESSION().query(models.RepoGroup).all():
+        ug.user_id = adm.user_id
+        _SESSION().add(ug)
+    _SESSION().commit()
--- a/rhodecode/lib/dbmigrate/versions/014_version_1_7_1.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/versions/014_version_1_7_1.py	Wed Jul 02 19:03:13 2014 -0400
@@ -23,16 +23,28 @@
     Don't create your own engine; bind migrate_engine to your metadata
     """
     _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_1_7_0
 
     #==========================================================================
     # Gist
     #==========================================================================
-    from rhodecode.lib.dbmigrate.schema.db_1_7_0 import Gist
-    tbl = Gist.__table__
+    tbl = db_1_7_0.Gist.__table__
     user_id = tbl.columns.gist_expires
     user_id.alter(type=Float(53))
 
+    # issue fixups
+    fixups(db_1_7_0, meta.Session)
+
 
 def downgrade(migrate_engine):
     meta = MetaData()
     meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    # fix nullable columns on last_update
+    for r in models.Repository().get_all():
+        if r.updated_on is None:
+            r.updated_on = datetime.datetime.fromtimestamp(0)
+            _SESSION().add(r)
+    _SESSION().commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/015_version_1_8_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,82 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_1_8_0
+    tbl = db_1_8_0.RhodeCodeSetting.__table__
+    app_settings_type = Column("app_settings_type",
+                               String(255, convert_unicode=False, assert_unicode=None),
+                               nullable=True, unique=None, default=None)
+    app_settings_type.create(table=tbl)
+
+    # issue fixups
+    fixups(db_1_8_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    notify('Fixing default options now...')
+
+    settings = [
+        #general
+        ('realm', '', 'unicode'),
+        ('title', '', 'unicode'),
+        ('ga_code', '', 'unicode'),
+        ('show_public_icon', False, 'bool'),
+        ('show_private_icon', True, 'bool'),
+        ('stylify_metatags', True, 'bool'),
+
+        # defaults
+        ('default_repo_enable_locking',  False, 'bool'),
+        ('default_repo_enable_downloads', False, 'bool'),
+        ('default_repo_enable_statistics', False, 'bool'),
+        ('default_repo_private', False, 'bool'),
+        ('default_repo_type', 'hg', 'unicode'),
+
+        #other
+        ('dashboard_items', 100, 'int'),
+        ('show_version', True, 'bool')
+    ]
+
+    for name, default, type_ in settings:
+        setting = models.RhodeCodeSetting.get_by_name(name)
+        if not setting:
+            # if we don't have this option create it
+            setting = models.RhodeCodeSetting(name, default, type_)
+
+        # fix certain key to new defaults
+        if name in ['title', 'show_public_icon']:
+            # change title if it's only the default
+            if name == 'title' and setting.app_settings_value == 'RhodeCode':
+                setting.app_settings_value = default
+            else:
+                setting.app_settings_value = default
+
+        setting._app_settings_type = type_
+        _SESSION().add(setting)
+        _SESSION().commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/016_version_2_0_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,69 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_0_0
+    tbl = db_2_0_0.User.__table__
+
+    extern_type = Column("extern_type",
+                         String(255, convert_unicode=False, assert_unicode=None),
+                         nullable=True, unique=None, default=None)
+    extern_type.create(table=tbl)
+
+    extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None),
+                         nullable=True, unique=None, default=None)
+    extern_name.create(table=tbl)
+
+    created_on = Column('created_on', DateTime(timezone=False),
+                        nullable=True, default=datetime.datetime.now)
+    created_on.create(table=tbl)
+
+    # issue fixups
+    fixups(db_2_0_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    notify('Fixing default created on')
+
+    for usr in models.User.get_all():
+        usr.created_on = datetime.datetime.now()
+        _SESSION().add(usr)
+        _SESSION().commit()
+
+    notify('Migrating LDAP attribute to extern')
+    for usr in models.User.get_all():
+        ldap_dn = usr.ldap_dn
+        if ldap_dn:
+            usr.extern_name = ldap_dn
+            usr.extern_type = 'ldap'
+        else:
+            usr.extern_name = 'rhodecode'
+            usr.extern_type = 'rhodecode'
+        _SESSION().add(usr)
+        _SESSION().commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/017_version_2_0_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,53 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_0_0
+    tbl = db_2_0_0.UserGroup.__table__
+
+    user_group_description = Column("user_group_description",
+                                    String(10000, convert_unicode=False,
+                                           assert_unicode=None), nullable=True,
+                                    unique=None, default=None)
+    user_group_description.create(table=tbl)
+
+    created_on = Column('created_on', DateTime(timezone=False),
+                        nullable=True, default=datetime.datetime.now)
+    created_on.create(table=tbl)
+
+    # issue fixups
+    fixups(db_2_0_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    notify('Fixing default created on')
+
+    for gr in models.UserGroup.get_all():
+        gr.created_on = datetime.datetime.now()
+        _SESSION().commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/018_version_2_0_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,78 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_0_0
+
+    # issue fixups
+    fixups(db_2_0_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    notify('Fixing default auth modules')
+    plugins = 'rhodecode.lib.auth_modules.auth_rhodecode'
+    opts = []
+    ldap_enabled = str2bool(getattr(
+        models.RhodeCodeSetting.get_by_name('ldap_active'),
+        'app_settings_value', False))
+    if ldap_enabled:
+        plugins += ',rhodecode.lib.auth_modules.auth_ldap'
+        opts.append(('auth_ldap_enabled', 'True', 'bool'))
+
+    opts.append(('auth_plugins', plugins, 'list'),)
+    opts.append(('auth_rhodecode_enabled', 'True', 'bool'))
+
+    for name, default, type_ in opts:
+        setting = models.RhodeCodeSetting.get_by_name(name)
+        if not setting:
+            # if we don't have this option create it
+            setting = models.RhodeCodeSetting(name, default, type_)
+
+        _SESSION().add(setting)
+        _SESSION().commit()
+
+    #copy over the LDAP settings
+    old_ldap = [('ldap_active', 'false', 'bool'), ('ldap_host', '', 'unicode'),
+                ('ldap_port', '389', 'int'), ('ldap_tls_kind', 'PLAIN', 'unicode'),
+                ('ldap_tls_reqcert', '', 'unicode'), ('ldap_dn_user', '', 'unicode'),
+                ('ldap_dn_pass', '', 'unicode'), ('ldap_base_dn', '', 'unicode'),
+                ('ldap_filter', '', 'unicode'), ('ldap_search_scope', '', 'unicode'),
+                ('ldap_attr_login', '', 'unicode'), ('ldap_attr_firstname', '', 'unicode'),
+                ('ldap_attr_lastname', '', 'unicode'), ('ldap_attr_email', '', 'unicode')]
+    for k, v, t in old_ldap:
+        old_setting = models.RhodeCodeSetting.get_by_name(k)
+        name = 'auth_%s' % k
+        setting = models.RhodeCodeSetting.get_by_name(name)
+        if not setting:
+            # if we don't have this option create it
+            setting = models.RhodeCodeSetting(name, old_setting.app_settings_value, t)
+
+        _SESSION().add(setting)
+        _SESSION().commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/019_version_2_0_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,42 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_0_0
+    tbl = db_2_0_0.RhodeCodeSetting.__table__
+    settings_value = tbl.columns.app_settings_value
+    settings_value.alter(type=String(4096))
+
+    # issue fixups
+    fixups(db_2_0_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    return
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/020_version_2_0_1.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,45 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_0_1
+
+    # issue fixups
+    fixups(db_2_0_1, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    #fix all empty extern type users to default 'rhodecode'
+    for usr in models.User.query().all():
+        if not usr.extern_name:
+            usr.extern_name = 'rhodecode'
+            usr.extern_type = 'rhodecode'
+            _SESSION().add(usr)
+            _SESSION().commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/021_version_2_0_2.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,76 @@
+import os
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_0_1
+    tbl = db_2_0_1.RepoGroup.__table__
+
+    created_on = Column('created_on', DateTime(timezone=False), nullable=True,
+                        default=datetime.datetime.now)
+    created_on.create(table=tbl)
+
+    #fix null values on certain columns when upgrading from older releases
+    tbl = db_2_0_1.UserLog.__table__
+    col = tbl.columns.user_id
+    col.alter(nullable=True)
+
+    tbl = db_2_0_1.UserFollowing.__table__
+    col = tbl.columns.follows_repository_id
+    col.alter(nullable=True)
+
+    tbl = db_2_0_1.UserFollowing.__table__
+    col = tbl.columns.follows_user_id
+    col.alter(nullable=True)
+
+    # issue fixups
+    fixups(db_2_0_1, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    notify('Fixing default created on for repo groups')
+
+    for gr in models.RepoGroup.get_all():
+        gr.created_on = datetime.datetime.now()
+        _SESSION().add(gr)
+        _SESSION().commit()
+
+    repo_store_path = models.RhodeCodeUi.get_repos_location()
+    _store = os.path.join(repo_store_path, '.cache', 'largefiles')
+    notify('Setting largefiles usercache')
+    print _store
+
+    if not models.RhodeCodeUi.get_by_key('usercache'):
+        largefiles = models.RhodeCodeUi()
+        largefiles.ui_section = 'largefiles'
+        largefiles.ui_key = 'usercache'
+        largefiles.ui_value = _store
+        _SESSION().add(largefiles)
+        _SESSION().commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/022_version_2_0_2.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,70 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_0_2
+
+    # issue fixups
+    fixups(db_2_0_2, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    notify('fixing new schema for landing_rev')
+
+    for repo in models.Repository.get_all():
+        print u'repo %s old landing rev is: %s' % (repo, repo.landing_rev)
+        _rev = repo.landing_rev[1]
+        _rev_type = 'rev'  # default
+
+        if _rev in ['default', 'master']:
+            _rev_type = 'branch'
+        elif _rev in ['tip']:
+            _rev_type = 'rev'
+        else:
+            try:
+                scm = repo.scm_instance
+                if scm:
+                    known_branches = scm.branches.keys()
+                    known_bookmarks = scm.bookmarks.keys()
+                    if _rev in known_branches:
+                        _rev_type = 'branch'
+                    elif _rev in known_bookmarks:
+                        _rev_type = 'book'
+            except Exception, e:
+                print e
+                print 'continue...'
+                #we don't want any error to break the process
+                pass
+
+        _new_landing_rev = '%s:%s' % (_rev_type, _rev)
+        print u'setting to %s' % _new_landing_rev
+        repo.landing_rev = _new_landing_rev
+        _SESSION().add(repo)
+        _SESSION().commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/023_version_2_1_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,42 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_1_0
+
+    tbl = db_2_1_0.UserApiKeys.__table__
+    tbl.create()
+
+    # issue fixups
+    fixups(db_2_1_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/024_version_2_1_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,67 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_1_0
+
+    # issue fixups
+    fixups(db_2_1_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    from pylons import config
+    from rhodecode.lib.utils2 import str2bool
+
+    notify('migrating options from .ini file')
+    use_gravatar = str2bool(config.get('use_gravatar'))
+    print('Setting gravatar use to: %s' % use_gravatar)
+    sett = models.RhodeCodeSetting.create_or_update('use_gravatar',
+                                                    use_gravatar, 'bool')
+    _SESSION().add(sett)
+    _SESSION.commit()
+    #set the new format of gravatar URL
+    gravatar_url = models.User.DEFAULT_GRAVATAR_URL
+    if config.get('alternative_gravatar_url'):
+        gravatar_url = config.get('alternative_gravatar_url')
+
+    print('Setting gravatar url to:%s' % gravatar_url)
+    sett = models.RhodeCodeSetting.create_or_update('gravatar_url',
+                                                    gravatar_url, 'unicode')
+    _SESSION().add(sett)
+    _SESSION.commit()
+
+    #now create new changed value of clone_url
+    clone_uri_tmpl = models.Repository.DEFAULT_CLONE_URI
+    print('settings new clone url template to %s' % clone_uri_tmpl)
+
+    sett = models.RhodeCodeSetting.create_or_update('clone_uri_tmpl',
+                                                    clone_uri_tmpl, 'unicode')
+    _SESSION().add(sett)
+    _SESSION.commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/025_version_2_1_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,43 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_1_0
+
+    # issue fixups
+    fixups(db_2_1_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    notify('Creating upgrade URL')
+    sett = models.RhodeCodeSetting.create_or_update('update_url',
+                            models.RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode')
+    _SESSION().add(sett)
+    _SESSION.commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/026_version_2_2_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,44 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_2_0
+
+    tbl = db_2_2_0.User.__table__
+
+    user_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data
+    user_data.create(table=tbl)
+
+    # issue fixups
+    fixups(db_2_2_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/027_version_2_2_0.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,59 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_2_0
+
+    # issue fixups
+    fixups(db_2_2_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    # ** create default permissions ** #
+    #=====================================
+    for p in models.Permission.PERMS:
+        if not models.Permission.get_by_key(p[0]):
+            new_perm = models.Permission()
+            new_perm.permission_name = p[0]
+            new_perm.permission_longname = p[0]  #translation err with p[1]
+            print 'Creating new permission %s' % p[0]
+            _SESSION().add(new_perm)
+
+    _SESSION().commit()
+
+    # ** set default create_on_write to active
+    user = models.User.get_default_user()
+    _def = 'hg.create.write_on_repogroup.true'
+    new = models.UserToPerm()
+    new.user = user
+    new.permission = models.Permission.get_by_key(_def)
+    print 'Setting default to %s' % _def
+    _SESSION().add(new)
+    _SESSION().commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/028_version_2_2_3.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,44 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_2_0
+
+    tbl = db_2_2_0.UserGroup.__table__
+
+    user_data = Column("group_data", LargeBinary(), nullable=True)  # JSON data
+    user_data.create(table=tbl)
+
+    # issue fixups
+    fixups(db_2_2_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/029_version_2_2_3.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,52 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_2_0
+
+    # issue fixups
+    fixups(db_2_2_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    notify('Adding grid items options now...')
+
+    settings = [
+        ('admin_grid_items', 25, 'int'),  # old hardcoded value was 25
+    ]
+
+    for name, default, type_ in settings:
+        setting = models.RhodeCodeSetting.get_by_name(name)
+        if not setting:
+            # if we don't have this option create it
+            setting = models.RhodeCodeSetting(name, default, type_)
+        setting._app_settings_type = type_
+        _SESSION().add(setting)
+        _SESSION().commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/030_version_2_2_3.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,44 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_2_0
+
+    tbl = db_2_2_0.Repository.__table__
+
+    repo_state = Column("repo_state", String(255), nullable=True)
+    repo_state.create(table=tbl)
+
+    # issue fixups
+    fixups(db_2_2_0, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/031_version_2_2_3.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,45 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+from rhodecode.lib.utils2 import str2bool
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base, notify
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    _reset_base(migrate_engine)
+    from rhodecode.lib.dbmigrate.schema import db_2_2_3
+
+    # issue fixups
+    fixups(db_2_2_3, meta.Session)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+    notify('Creating repository states')
+    for repo in models.Repository.get_all():
+        _state = models.Repository.STATE_CREATED
+        print 'setting repo %s state to "%s"' % (repo, _state)
+        repo.repo_state = _state
+        _SESSION().add(repo)
+        _SESSION().commit()
--- a/rhodecode/lib/dbmigrate/versions/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/dbmigrate/versions/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.dbmigrate.versions.__init__
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Package containing new versions of database models
-
-    :created_on: Dec 11, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,23 +11,50 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.dbmigrate.versions.__init__
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Package containing new versions of database models
+
+:created_on: Dec 11, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 from sqlalchemy import *
 from sqlalchemy.exc import DatabaseError
 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
-from sqlalchemy.orm.session import Session
 from sqlalchemy.ext.declarative import declarative_base
-
+from sqlalchemy.orm import scoped_session, sessionmaker
 from rhodecode.lib.dbmigrate.migrate import *
 from rhodecode.lib.dbmigrate.migrate.changeset import *
 
-from rhodecode.model.meta import Base
 from rhodecode.model import meta
 
 
+def notify(msg, caps=True):
+    """
+    Notification for migrations messages
+    """
+    ml = len(msg) + (4 * 2)
+    formatted_msg = ('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml))
+    if caps:
+        formatted_msg = formatted_msg.upper()
+    print(formatted_msg)
+
+
 def _reset_base(migrate_engine):
     ## RESET COMPLETLY THE metadata for sqlalchemy to use previous declared Base
     Base = declarative_base()
     Base.metadata.clear()
     Base.metadata = MetaData()
     Base.metadata.bind = migrate_engine
+
+    # new session and base
+    #meta.Session = scoped_session(sessionmaker(expire_on_commit=True,))
+    #meta.Session.configure(bind=migrate_engine)
     meta.Base = Base
+
+    notify('SQLA BASE RESET !')
--- a/rhodecode/lib/diffs.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/diffs.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,17 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.diffs
-    ~~~~~~~~~~~~~~~~~~~
-
-    Set of diffing helpers, previously part of vcs
-
-
-    :created_on: Dec 4, 2011
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :original copyright: 2007-2008 by Armin Ronacher
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -24,7 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.diffs
+~~~~~~~~~~~~~~~~~~~
 
+Set of diffing helpers, previously part of vcs
+
+
+:created_on: Dec 4, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 import re
 import difflib
 import logging
@@ -438,7 +436,9 @@
                 chunks = []
 
             if op == 'D' and chunks:
-                chunks = []
+                # if not full diff mode show deleted file contents
+                if self.diff_limit is not None:
+                    chunks = []
 
             chunks.insert(0, [{
                 'old_lineno': '',
--- a/rhodecode/lib/exceptions.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/exceptions.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.exceptions
-    ~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Set of custom exceptions used in RhodeCode
-
-    :created_on: Nov 17, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.exceptions
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Set of custom exceptions used in RhodeCode
+
+:created_on: Nov 17, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 from webob.exc import HTTPClientError
 
--- a/rhodecode/lib/graphmod.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/graphmod.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,16 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 Modified mercurial DAG graph functions that re-uses VCS structure
 
@@ -36,7 +49,7 @@
         repo = repo
     elif alias == 'git':
         def cl(rev):
-            return [x.revision for x in repo[rev].parents()]
+            return [x.revision for x in repo[rev].parents]
         repo = repo
 
     lowestrev = min(revs)
--- a/rhodecode/lib/helpers.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/helpers.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,4 +1,18 @@
-"""Helper functions
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Helper functions
 
 Consists of functions to typically be used within templates, but also
 available to Controllers. This module is available to both as 'h'.
@@ -365,8 +379,8 @@
     """
 
     def __init__(self, category, message):
-        self.category=category
-        self.message=message
+        self.category = category
+        self.message = message
 
     def __str__(self):
         return self.message
@@ -475,6 +489,10 @@
     # attr to return from fetched user
     person_getter = lambda usr: getattr(usr, show_attr)
 
+    # if author is already an instance use it for extraction
+    if isinstance(author, User):
+        return person_getter(author)
+
     # Valid email in the attribute passed, see if they're in the system
     _email = email(author)
     if _email != '':
@@ -512,6 +530,9 @@
 
     :param value:
     """
+    if not value:
+        return ''
+
     value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
                    '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
     value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
@@ -534,11 +555,9 @@
     """
 
     if value:
-        return HTML.tag('img', src=url("/images/icons/accept.png"),
-                        alt=_('True'))
+        return HTML.tag('i', class_="icon-ok-sign")
     else:
-        return HTML.tag('img', src=url("/images/icons/cancel.png"),
-                        alt=_('False'))
+        return HTML.tag('i', class_="icon-minus-sign")
 
 
 def action_parser(user_log, feed=False, parse_cs=False):
@@ -722,51 +741,51 @@
     # action : translated str, callback(extractor), icon
     action_map = {
     'user_deleted_repo':           (_('[deleted] repository'),
-                                    None, 'database_delete.png'),
+                                    None, 'icon-trash'),
     'user_created_repo':           (_('[created] repository'),
-                                    None, 'database_add.png'),
+                                    None, 'icon-plus icon-plus-colored'),
     'user_created_fork':           (_('[created] repository as fork'),
-                                    None, 'arrow_divide.png'),
+                                    None, 'icon-code-fork'),
     'user_forked_repo':            (_('[forked] repository'),
-                                    get_fork_name, 'arrow_divide.png'),
+                                    get_fork_name, 'icon-code-fork'),
     'user_updated_repo':           (_('[updated] repository'),
-                                    None, 'database_edit.png'),
+                                    None, 'icon-pencil icon-pencil-colored'),
     'user_downloaded_archive':      (_('[downloaded] archive from repository'),
-                                    get_archive_name, 'page_white_compressed.png'),
+                                    get_archive_name, 'icon-download-alt'),
     'admin_deleted_repo':          (_('[delete] repository'),
-                                    None, 'database_delete.png'),
+                                    None, 'icon-trash'),
     'admin_created_repo':          (_('[created] repository'),
-                                    None, 'database_add.png'),
+                                    None, 'icon-plus icon-plus-colored'),
     'admin_forked_repo':           (_('[forked] repository'),
-                                    None, 'arrow_divide.png'),
+                                    None, 'icon-code-fork icon-fork-colored'),
     'admin_updated_repo':          (_('[updated] repository'),
-                                    None, 'database_edit.png'),
+                                    None, 'icon-pencil icon-pencil-colored'),
     'admin_created_user':          (_('[created] user'),
-                                    get_user_name, 'user_add.png'),
+                                    get_user_name, 'icon-user icon-user-colored'),
     'admin_updated_user':          (_('[updated] user'),
-                                    get_user_name, 'user_edit.png'),
+                                    get_user_name, 'icon-user icon-user-colored'),
     'admin_created_users_group':   (_('[created] user group'),
-                                    get_users_group, 'group_add.png'),
+                                    get_users_group, 'icon-pencil icon-pencil-colored'),
     'admin_updated_users_group':   (_('[updated] user group'),
-                                    get_users_group, 'group_edit.png'),
+                                    get_users_group, 'icon-pencil icon-pencil-colored'),
     'user_commented_revision':     (_('[commented] on revision in repository'),
-                                    get_cs_links, 'comment_add.png'),
+                                    get_cs_links, 'icon-comment icon-comment-colored'),
     'user_commented_pull_request': (_('[commented] on pull request for'),
-                                    get_pull_request, 'comment_add.png'),
+                                    get_pull_request, 'icon-comment icon-comment-colored'),
     'user_closed_pull_request':    (_('[closed] pull request for'),
-                                    get_pull_request, 'tick.png'),
+                                    get_pull_request, 'icon-check'),
     'push':                        (_('[pushed] into'),
-                                    get_cs_links, 'script_add.png'),
+                                    get_cs_links, 'icon-arrow-up'),
     'push_local':                  (_('[committed via RhodeCode] into repository'),
-                                    get_cs_links, 'script_edit.png'),
+                                    get_cs_links, 'icon-pencil icon-pencil-colored'),
     'push_remote':                 (_('[pulled from remote] into repository'),
-                                    get_cs_links, 'connect.png'),
+                                    get_cs_links, 'icon-arrow-up'),
     'pull':                        (_('[pulled] from'),
-                                    None, 'down_16.png'),
+                                    None, 'icon-arrow-down'),
     'started_following_repo':      (_('[started following] repository'),
-                                    None, 'heart_add.png'),
+                                    None, 'icon-heart icon-heart-colored'),
     'stopped_following_repo':      (_('[stopped following] repository'),
-                                    None, 'heart_delete.png'),
+                                    None, 'icon-heart-empty icon-heart-colored'),
     }
 
     action_str = action_map.get(action, action)
@@ -790,9 +809,9 @@
         if len(x) > 1:
             action, action_params = x
 
-        tmpl = """<img src="%s%s" alt="%s"/>"""
+        tmpl = """<i class="%s" alt="%s"></i>"""
         ico = action_map.get(action, ['', '', ''])[2]
-        return literal(tmpl % ((url('/images/icons/')), ico, action))
+        return literal(tmpl % (ico, action))
 
     # returned callbacks we need to call to get
     return [lambda: literal(action), action_params_func, action_parser_icon]
@@ -803,8 +822,8 @@
 # PERMS
 #==============================================================================
 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
-HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
-HasReposGroupPermissionAny
+HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
+HasRepoGroupPermissionAny
 
 
 #==============================================================================
@@ -812,42 +831,36 @@
 #==============================================================================
 
 def gravatar_url(email_address, size=30, ssl_enabled=True):
-    from pylons import url  # doh, we need to re-import url to mock it later
-    from rhodecode import CONFIG
+    # doh, we need to re-import those to mock it later
+    from pylons import url
+    from pylons import tmpl_context as c
 
     _def = 'anonymous@rhodecode.org'  # default gravatar
-    use_gravatar = str2bool(CONFIG.get('use_gravatar'))
-    alternative_gravatar_url = CONFIG.get('alternative_gravatar_url', '')
+    _use_gravatar = c.visual.use_gravatar
+    _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
+
     email_address = email_address or _def
-    if not use_gravatar or not email_address or email_address == _def:
+    if isinstance(email_address, unicode):
+        #hashlib crashes on unicode items
+        email_address = safe_str(email_address)
+
+    if not _use_gravatar or not email_address or email_address == _def:
+        # pick best matching size to one given in size param
         f = lambda a, l: min(l, key=lambda x: abs(x - a))
         return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
 
-    if use_gravatar and alternative_gravatar_url:
-        tmpl = alternative_gravatar_url
+    if _use_gravatar:
+        _md5 = lambda s: hashlib.md5(s).hexdigest()
+
+        tmpl = _gravatar_url
         parsed_url = urlparse.urlparse(url.current(qualified=True))
         tmpl = tmpl.replace('{email}', email_address)\
-                   .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
+                   .replace('{md5email}', _md5(email_address.lower())) \
                    .replace('{netloc}', parsed_url.netloc)\
                    .replace('{scheme}', parsed_url.scheme)\
-                   .replace('{size}', str(size))
+                   .replace('{size}', safe_str(size))
         return tmpl
 
-    default = 'identicon'
-    baseurl_nossl = "http://www.gravatar.com/avatar/"
-    baseurl_ssl = "https://secure.gravatar.com/avatar/"
-    baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
-
-    if isinstance(email_address, unicode):
-        #hashlib crashes on unicode items
-        email_address = safe_str(email_address)
-    # construct the url
-    gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
-    gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
-
-    return gravatar_url
-
-
 class Page(_Page):
     """
     Custom pager to match rendering style with YUI paginator
@@ -934,7 +947,9 @@
             nav_items.append(self._pagerlink(self.last_page, self.last_page))
 
         ## prerender links
-        nav_items.append(literal('<link rel="prerender" href="/rhodecode/changelog/1?page=%s">' % str(int(self.page)+1)))
+        #_page_link = url.current()
+        #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
+        #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
         return self.separator.join(nav_items)
 
     def pager(self, format='~2~', page_param='page', partial_param='partial',
--- a/rhodecode/lib/hooks.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/hooks.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.hooks
-    ~~~~~~~~~~~~~~~~~~~
-
-    Hooks runned by rhodecode
-
-    :created_on: Aug 6, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,18 +11,27 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.hooks
+~~~~~~~~~~~~~~~~~~~
+
+Hooks runned by rhodecode
+
+:created_on: Aug 6, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import os
 import sys
 import time
 import binascii
-import traceback
-from inspect import isfunction
 
 from rhodecode.lib.vcs.utils.hgcompat import nullrev, revrange
 from rhodecode.lib import helpers as h
 from rhodecode.lib.utils import action_logger
 from rhodecode.lib.vcs.backends.base import EmptyChangeset
-from rhodecode.lib.compat import json
 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
 from rhodecode.lib.utils2 import safe_str, _extract_extras
 from rhodecode.model.db import Repository, User
@@ -136,7 +134,7 @@
     # extension hook call
     from rhodecode import EXTENSIONS
     callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
-    if isfunction(callback):
+    if callable(callback):
         kw = {}
         kw.update(ex)
         callback(**kw)
@@ -165,8 +163,8 @@
 
     ex = _extract_extras()
 
-    action = ex.action + ':%s'
-
+    action_tmpl = ex.action + ':%s'
+    revs = []
     if ex.scm == 'hg':
         node = kwargs['node']
 
@@ -176,26 +174,25 @@
 
                 if len(revs) == 0:
                     return (nullrev, nullrev)
-                return (max(revs), min(revs))
+                return max(revs), min(revs)
             else:
-                return (len(repo) - 1, 0)
+                return len(repo) - 1, 0
 
         stop, start = get_revs(repo, [node + ':'])
-        h = binascii.hexlify
-        revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
+        _h = binascii.hexlify
+        revs = [_h(repo[r].node()) for r in xrange(start, stop + 1)]
     elif ex.scm == 'git':
         revs = kwargs.get('_git_revs', [])
         if '_git_revs' in kwargs:
             kwargs.pop('_git_revs')
 
-    action = action % ','.join(revs)
-
+    action = action_tmpl % ','.join(revs)
     action_logger(ex.username, action, ex.repository, ex.ip, commit=True)
 
     # extension hook call
     from rhodecode import EXTENSIONS
     callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
-    if isfunction(callback):
+    if callable(callback):
         kw = {'pushed_revs': revs}
         kw.update(ex)
         callback(**kw)
@@ -242,7 +239,7 @@
     """
     from rhodecode import EXTENSIONS
     callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
-    if isfunction(callback):
+    if callable(callback):
         kw = {}
         kw.update(repository_dict)
         kw.update({'created_by': created_by})
@@ -253,13 +250,38 @@
 
 
 def check_allowed_create_user(user_dict, created_by, **kwargs):
+    # pre create hooks
     from rhodecode import EXTENSIONS
     callback = getattr(EXTENSIONS, 'PRE_CREATE_USER_HOOK', None)
-    if isfunction(callback):
+    if callable(callback):
         allowed, reason = callback(created_by=created_by, **user_dict)
         if not allowed:
             raise UserCreationError(reason)
 
+    # license limit hook
+    import rhodecode
+    from rhodecode.model.license import LicenseModel
+    license_token = rhodecode.CONFIG.get('license_token')
+    license_key = LicenseModel.get_license_key()
+    license_info = LicenseModel.get_license_info(
+        license_token=license_token, enc_license_key=license_key,
+        fill_defaults=True)
+    expiration_check = False
+    if expiration_check:
+        now = time.time()
+        #check expiration
+        if now > license_info['valid_till']:
+            reason = ('Your license has expired, '
+                      'please contact support to extend your license.')
+            raise UserCreationError(reason)
+    # user count check
+    cur_user_count = User.query().count()
+    if cur_user_count > int(license_info['users']) > 0:
+        reason = ('You have reached the maximum number of users (%s), '
+                  'please contact support to extend your license.'
+                  % license_info['users'])
+        raise UserCreationError(reason)
+
 
 def log_create_user(user_dict, created_by, **kwargs):
     """
@@ -294,7 +316,7 @@
     """
     from rhodecode import EXTENSIONS
     callback = getattr(EXTENSIONS, 'CREATE_USER_HOOK', None)
-    if isfunction(callback):
+    if callable(callback):
         return callback(created_by=created_by, **user_dict)
 
     return 0
@@ -327,7 +349,7 @@
     """
     from rhodecode import EXTENSIONS
     callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None)
-    if isfunction(callback):
+    if callable(callback):
         kw = {}
         kw.update(repository_dict)
         kw.update({'deleted_by': deleted_by,
@@ -371,7 +393,7 @@
     """
     from rhodecode import EXTENSIONS
     callback = getattr(EXTENSIONS, 'DELETE_USER_HOOK', None)
-    if isfunction(callback):
+    if callable(callback):
         return callback(deleted_by=deleted_by, **user_dict)
 
     return 0
@@ -404,7 +426,8 @@
 
     path, ini_name = os.path.split(extras['config'])
     conf = appconfig('config:%s' % ini_name, relative_to=path)
-    load_environment(conf.global_conf, conf.local_conf)
+    load_environment(conf.global_conf, conf.local_conf, test_env=False,
+                     test_index=False)
 
     engine = engine_from_config(conf, 'sqlalchemy.db1.')
     init_model(engine)
@@ -432,7 +455,6 @@
 
     # if push hook is enabled via web interface
     elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
-
         rev_data = []
         for l in revs:
             old_rev, new_rev, ref = l.split(' ')
@@ -445,10 +467,16 @@
                                  'name': _ref_data[2].strip()})
 
         git_revs = []
-        for push_ref  in rev_data:
+
+        for push_ref in rev_data:
             _type = push_ref['type']
             if _type == 'heads':
                 if push_ref['old_rev'] == EmptyChangeset().raw_id:
+                    # update the symbolic ref if we push new repo
+                    if repo.is_empty():
+                        repo._repo.refs.set_symbolic_ref('HEAD',
+                                            'refs/heads/%s' % push_ref['name'])
+
                     cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
                     heads = repo.run_git_command(cmd)[0]
                     heads = heads.replace(push_ref['ref'], '')
--- a/rhodecode/lib/indexers/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/indexers/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.indexers.__init__
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Whoosh indexing module for RhodeCode
-
-    :created_on: Aug 17, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.indexers.__init__
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Whoosh indexing module for RhodeCode
+
+:created_on: Aug 17, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import os
 import sys
 import logging
@@ -144,7 +145,7 @@
             res.update({'content_short': content_short,
                         'content_short_hl': self.highlight(content_short),
                         'f_path': f_path
-                      })
+            })
         elif self.search_type == 'path':
             full_repo_path = jn(self.repo_location, res['repository'])
             f_path = res['path'].split(full_repo_path)[-1]
--- a/rhodecode/lib/indexers/daemon.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/indexers/daemon.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.indexers.daemon
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    A daemon will read from task table and run tasks
-
-    :created_on: Jan 26, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.indexers.daemon
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A daemon will read from task table and run tasks
+
+:created_on: Jan 26, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 from __future__ import with_statement
 
 import os
@@ -111,14 +112,16 @@
             self.initial = False
 
     def _get_index_revision(self, repo):
-        db_repo = Repository.get_by_repo_name(repo.name)
+        db_repo = Repository.get_by_repo_name(repo.name_unicode)
         landing_rev = 'tip'
         if db_repo:
-            landing_rev = db_repo.landing_rev
+            _rev_type, _rev = db_repo.landing_rev
+            landing_rev = _rev
         return landing_rev
 
-    def _get_index_changeset(self, repo):
-        index_rev = self._get_index_revision(repo)
+    def _get_index_changeset(self, repo, index_rev=None):
+        if not index_rev:
+            index_rev = self._get_index_revision(repo)
         cs = repo.get_changeset(index_rev)
         return cs
 
@@ -139,7 +142,7 @@
             pass
         return index_paths_
 
-    def get_node(self, repo, path):
+    def get_node(self, repo, path, index_rev=None):
         """
         gets a filenode based on given full path.It operates on string for
         hg git compatability.
@@ -150,20 +153,20 @@
         """
         root_path = safe_str(repo.path)+'/'
         parts = safe_str(path).partition(root_path)
-        cs = self._get_index_changeset(repo)
+        cs = self._get_index_changeset(repo, index_rev=index_rev)
         node = cs.get_node(parts[-1])
         return node
 
     def get_node_mtime(self, node):
         return mktime(node.last_changeset.date.timetuple())
 
-    def add_doc(self, writer, path, repo, repo_name):
+    def add_doc(self, writer, path, repo, repo_name, index_rev=None):
         """
         Adding doc to writer this function itself fetches data from
         the instance of vcs backend
         """
 
-        node = self.get_node(repo, path)
+        node = self.get_node(repo, path, index_rev)
         indexed = indexed_w_content = 0
         # we just index the content of chosen files, and skip binary files
         if node.extension in INDEX_EXTENSIONS and not node.is_binary:
@@ -249,8 +252,9 @@
         i_cnt = iwc_cnt = 0
         log.debug('building index for %s @revision:%s' % (repo.path,
                                                 self._get_index_revision(repo)))
+        index_rev = self._get_index_revision(repo)
         for idx_path in self.get_paths(repo):
-            i, iwc = self.add_doc(file_idx_writer, idx_path, repo, repo_name)
+            i, iwc = self.add_doc(file_idx_writer, idx_path, repo, repo_name, index_rev)
             i_cnt += i
             iwc_cnt += iwc
 
--- a/rhodecode/lib/markup_renderer.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/markup_renderer.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.markup_renderer
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
-    Renderer for markup languages with ability to parse using rst or markdown
-
-    :created_on: Oct 27, 2011
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.markup_renderer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Renderer for markup languages with ability to parse using rst or markdown
+
+:created_on: Oct 27, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import re
 import logging
@@ -120,9 +120,11 @@
         return readme_data
 
     @classmethod
-    def plain(cls, source):
+    def plain(cls, source, universal_newline=True):
         source = safe_unicode(source)
-
+        if universal_newline:
+            newline = '\n'
+            source = newline.join(source.splitlines())
         def urlify_text(text):
             url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'
                                  '|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
@@ -150,7 +152,8 @@
         except Exception:
             log.error(traceback.format_exc())
             if safe:
-                return source
+                log.debug('Fallbacking to render in plain mode')
+                return cls.plain(source)
             else:
                 raise
 
@@ -180,7 +183,8 @@
         except Exception:
             log.error(traceback.format_exc())
             if safe:
-                return source
+                log.debug('Fallbacking to render in plain mode')
+                return cls.plain(source)
             else:
                 raise
 
--- a/rhodecode/lib/middleware/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/middleware/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
--- a/rhodecode/lib/middleware/errormator.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/middleware/errormator.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.middleware.errormator
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    middleware to handle errormator publishing of errors
-
-    :created_on: October 18, 2012
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.middleware.errormator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+middleware to handle errormator publishing of errors
+
+:created_on: October 18, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 try:
     from errormator_client import make_errormator_middleware
--- a/rhodecode/lib/middleware/https_fixup.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/middleware/https_fixup.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.middleware.https_fixup
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    middleware to handle https correctly
-
-    :created_on: May 23, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.middleware.https_fixup
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+middleware to handle https correctly
+
+:created_on: May 23, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 from rhodecode.lib.utils2 import str2bool
 
--- a/rhodecode/lib/middleware/pygrack.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/middleware/pygrack.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,7 +1,6 @@
 import os
 import socket
 import logging
-import subprocess
 import traceback
 
 from webob import Request, Response, exc
@@ -86,7 +85,7 @@
         try:
             out = subprocessio.SubprocessIOChunker(
                 r'%s %s --stateless-rpc --advertise-refs "%s"' % (
-                            _git_path, git_command[4:], self.content_path),
+                    _git_path, git_command[4:], self.content_path),
                 starting_values=[
                     packet_len + server_advert + '0000'
                 ]
@@ -107,6 +106,7 @@
         returns an iterator obj with contents of git command's
         response to stdout
         """
+        _git_path = rhodecode.CONFIG.get('git_path', 'git')
         git_command = self._get_fixedpath(request.path_info)
         if git_command not in self.commands:
             log.debug('command %s not allowed' % git_command)
@@ -124,10 +124,10 @@
             gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
             opts = dict(
                 env=gitenv,
-                cwd=os.getcwd()
+                cwd=self.content_path,
             )
-            cmd = r'git %s --stateless-rpc "%s"' % (git_command[4:],
-                                                    self.content_path),
+            cmd = r'%s %s --stateless-rpc "%s"' % (_git_path, git_command[4:],
+                                                   self.content_path),
             log.debug('handling cmd %s' % cmd)
             out = subprocessio.SubprocessIOChunker(
                 cmd,
@@ -141,11 +141,11 @@
         if git_command in [u'git-receive-pack']:
             # updating refs manually after each push.
             # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
-            _git_path = rhodecode.CONFIG.get('git_path', 'git')
-            cmd = (u'%s --git-dir "%s" '
-                    'update-server-info' % (_git_path, self.content_path))
-            log.debug('handling cmd %s' % cmd)
-            subprocess.call(cmd, shell=True)
+            from rhodecode.lib.vcs import get_repo
+            from dulwich.server import update_server_info
+            repo = get_repo(self.content_path)
+            if repo:
+                update_server_info(repo._repo)
 
         resp = Response()
         resp.content_type = 'application/x-%s-result' % git_command.encode('utf8')
@@ -197,4 +197,6 @@
 
 
 def make_wsgi_app(repo_name, repo_root, extras):
-    return GitDirectory(repo_root, repo_name, extras)
+    from dulwich.web import LimitedInputFilter, GunzipFilter
+    app = GitDirectory(repo_root, repo_name, extras)
+    return GunzipFilter(LimitedInputFilter(app))
--- a/rhodecode/lib/middleware/sentry.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/middleware/sentry.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.middleware.sentry
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    middleware to handle sentry/raven publishing of errors
-
-    :created_on: September 18, 2012
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.middleware.sentry
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+middleware to handle sentry/raven publishing of errors
+
+:created_on: September 18, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 try:
     from raven.base import Client
--- a/rhodecode/lib/middleware/simplegit.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/middleware/simplegit.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.middleware.simplegit
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    SimpleGit middleware for handling git protocol request (push/clone etc.)
-    It's implemented with basic auth function
-
-    :created_on: Apr 28, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,68 +11,38 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.middleware.simplegit
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+SimpleGit middleware for handling git protocol request (push/clone etc.)
+It's implemented with basic auth function
+
+:created_on: Apr 28, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+"""
+
 
 import os
 import re
 import logging
 import traceback
 
-from dulwich import server as dulserver
-from dulwich.web import LimitedInputFilter, GunzipFilter
-from rhodecode.lib.exceptions import HTTPLockedRC
-from rhodecode.lib.hooks import pre_pull
-
-
-class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
-
-    def handle(self):
-        write = lambda x: self.proto.write_sideband(1, x)
-
-        graph_walker = dulserver.ProtocolGraphWalker(self,
-                                                     self.repo.object_store,
-                                                     self.repo.get_peeled)
-        objects_iter = self.repo.fetch_objects(
-          graph_walker.determine_wants, graph_walker, self.progress,
-          get_tagged=self.get_tagged)
-
-        # Did the process short-circuit (e.g. in a stateless RPC call)? Note
-        # that the client still expects a 0-object pack in most cases.
-        if objects_iter is None:
-            return
-
-        self.progress("counting objects: %d, done.\n" % len(objects_iter))
-        dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
-                                     objects_iter)
-        messages = ['thank you for using rhodecode']
-
-        for msg in messages:
-            self.progress(msg + "\n")
-        # we are done
-        self.proto.write("0000")
-
-
-dulserver.DEFAULT_HANDLERS = {
-  #git-ls-remote, git-clone, git-fetch and git-pull
-  'git-upload-pack': SimpleGitUploadPackHandler,
-  #git-push
-  'git-receive-pack': dulserver.ReceivePackHandler,
-}
-
-# not used for now until dulwich gets fixed
-#from dulwich.repo import Repo
-#from dulwich.web import make_wsgi_chain
-
 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
-    HTTPBadRequest, HTTPNotAcceptable
+    HTTPNotAcceptable
+from rhodecode.model.db import User, RhodeCodeUi
 
 from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url,\
     _set_extras
 from rhodecode.lib.base import BaseVCSController
-from rhodecode.lib.auth import get_container_username
-from rhodecode.lib.utils import is_valid_repo, make_ui
-from rhodecode.lib.compat import json
-from rhodecode.model.db import User, RhodeCodeUi
+from rhodecode.lib.utils import make_ui, is_valid_repo
+from rhodecode.lib.exceptions import HTTPLockedRC
+from rhodecode.lib.hooks import pre_pull
+from rhodecode.lib import auth_modules
 
 log = logging.getLogger(__name__)
 
@@ -139,23 +97,34 @@
         if action in ['pull', 'push']:
             anonymous_user = self.__get_user('default')
             username = anonymous_user.username
-            anonymous_perm = self._check_permission(action, anonymous_user,
-                                                    repo_name, ip_addr)
+            if anonymous_user.active:
+                # ONLY check permissions if the user is activated
+                anonymous_perm = self._check_permission(action, anonymous_user,
+                                                        repo_name, ip_addr)
+            else:
+                anonymous_perm = False
 
-            if not anonymous_perm or not anonymous_user.active:
+            if not anonymous_user.active or not anonymous_perm:
+                if not anonymous_user.active:
+                    log.debug('Anonymous access is disabled, running '
+                              'authentication')
+
                 if not anonymous_perm:
                     log.debug('Not enough credentials to access this '
                               'repository as anonymous user')
-                if not anonymous_user.active:
-                    log.debug('Anonymous access is disabled, running '
-                              'authentication')
+
+                username = None
                 #==============================================================
                 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
                 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
                 #==============================================================
 
-                # Attempting to retrieve username from the container
-                username = get_container_username(environ, self.config)
+                # try to auth based on environ, container auth methods
+                log.debug('Running PRE-AUTH for container based authentication')
+                pre_auth = auth_modules.authenticate('', '', environ)
+                if pre_auth and pre_auth.get('username'):
+                    username = pre_auth['username']
+                log.debug('PRE-AUTH got %s as username' % username)
 
                 # If not authenticated by the container, running basic auth
                 if not username:
@@ -259,7 +228,6 @@
             repo_name=repo_name,
             extras=extras,
         )
-        app = GunzipFilter(LimitedInputFilter(app))
         return app
 
     def __get_repository(self, environ):
--- a/rhodecode/lib/middleware/simplehg.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/middleware/simplehg.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.middleware.simplehg
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    SimpleHG middleware for handling mercurial protocol request
-    (push/clone etc.). It's implemented with basic auth function
-
-    :created_on: Apr 28, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,26 +11,37 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.middleware.simplehg
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+SimpleHG middleware for handling mercurial protocol request
+(push/clone etc.). It's implemented with basic auth function
+
+:created_on: Apr 28, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+"""
+
 
 import os
 import logging
 import traceback
 
-
 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
-    HTTPBadRequest, HTTPNotAcceptable
+    HTTPNotAcceptable
+from rhodecode.model.db import User
 
 from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url,\
     _set_extras
 from rhodecode.lib.base import BaseVCSController
-from rhodecode.lib.auth import get_container_username
 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
-from rhodecode.lib.compat import json
 from rhodecode.lib.vcs.utils.hgcompat import RepoError, hgweb_mod
-from rhodecode.model.db import User
 from rhodecode.lib.exceptions import HTTPLockedRC
-
+from rhodecode.lib import auth_modules
 
 log = logging.getLogger(__name__)
 
@@ -102,23 +101,34 @@
         if action in ['pull', 'push']:
             anonymous_user = self.__get_user('default')
             username = anonymous_user.username
-            anonymous_perm = self._check_permission(action, anonymous_user,
-                                                    repo_name, ip_addr)
+            if anonymous_user.active:
+                # ONLY check permissions if the user is activated
+                anonymous_perm = self._check_permission(action, anonymous_user,
+                                                        repo_name, ip_addr)
+            else:
+                anonymous_perm = False
 
-            if not anonymous_perm or not anonymous_user.active:
+            if not anonymous_user.active or not anonymous_perm:
+                if not anonymous_user.active:
+                    log.debug('Anonymous access is disabled, running '
+                              'authentication')
+
                 if not anonymous_perm:
                     log.debug('Not enough credentials to access this '
                               'repository as anonymous user')
-                if not anonymous_user.active:
-                    log.debug('Anonymous access is disabled, running '
-                              'authentication')
+
+                username = None
                 #==============================================================
                 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
                 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
                 #==============================================================
 
-                # Attempting to retrieve username from the container
-                username = get_container_username(environ, self.config)
+                # try to auth based on environ, container auth methods
+                log.debug('Running PRE-AUTH for container based authentication')
+                pre_auth = auth_modules.authenticate('', '', environ)
+                if pre_auth and pre_auth.get('username'):
+                    username = pre_auth['username']
+                log.debug('PRE-AUTH got %s as username' % username)
 
                 # If not authenticated by the container, running basic auth
                 if not username:
--- a/rhodecode/lib/middleware/wrapper.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/middleware/wrapper.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.middleware.wrapper
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    request time mesuring app
-
-    :created_on: May 23, 2013
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.middleware.wrapper
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+request time mesuring app
+
+:created_on: May 23, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import time
 import logging
 from rhodecode.lib.base import _get_ip_addr, _get_access_path
--- a/rhodecode/lib/paster_commands/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/paster_commands/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
--- a/rhodecode/lib/paster_commands/cache_keys.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/paster_commands/cache_keys.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.paster_commands.cache_keys
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    cleanup-keys paster command for RhodeCode
-
-
-    :created_on: mar 27, 2013
-    :author: marcink
-    :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,6 +11,19 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.paster_commands.cache_keys
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+cleanup-keys paster command for RhodeCode
+
+
+:created_on: mar 27, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 from __future__ import with_statement
 
 import os
--- a/rhodecode/lib/paster_commands/cleanup.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,152 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.paster_commands.cleanup
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    cleanup-repos paster command for RhodeCode
-
-
-    :created_on: Jul 14, 2012
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-from __future__ import with_statement
-
-import os
-import sys
-import re
-import shutil
-import logging
-import datetime
-
-from rhodecode.lib.utils import BasePasterCommand, ask_ok, REMOVED_REPO_PAT
-from rhodecode.lib.utils2 import safe_str
-from rhodecode.model.db import RhodeCodeUi
-
-# fix rhodecode import
-from os.path import dirname as dn
-rc_path = dn(dn(dn(os.path.realpath(__file__))))
-sys.path.append(rc_path)
-
-log = logging.getLogger(__name__)
-
-
-class Command(BasePasterCommand):
-
-    max_args = 1
-    min_args = 1
-
-    usage = "CONFIG_FILE"
-    group_name = "RhodeCode"
-    takes_config_file = -1
-    parser = BasePasterCommand.standard_parser(verbose=True)
-    summary = "Cleanup deleted repos"
-
-    def _parse_older_than(self, val):
-        regex = re.compile(r'((?P<days>\d+?)d)?((?P<hours>\d+?)h)?((?P<minutes>\d+?)m)?((?P<seconds>\d+?)s)?')
-        parts = regex.match(val)
-        if not parts:
-            return
-        parts = parts.groupdict()
-        time_params = {}
-        for (name, param) in parts.iteritems():
-            if param:
-                time_params[name] = int(param)
-        return datetime.timedelta(**time_params)
-
-    def _extract_date(self, name):
-        """
-        Extract the date part from rm__<date> pattern of removed repos,
-        and convert it to datetime object
-
-        :param name:
-        """
-        date_part = name[4:19]  # 4:19 since we don't parse milisecods
-        return datetime.datetime.strptime(date_part, '%Y%m%d_%H%M%S')
-
-    def command(self):
-        #get SqlAlchemy session
-        self._init_session()
-
-        repos_location = RhodeCodeUi.get_repos_location()
-        to_remove = []
-        for dn, dirs, f in os.walk(safe_str(repos_location)):
-            alldirs = list(dirs)
-            del dirs[:]
-            if ('.hg' in alldirs or
-                'objects' in alldirs and ('refs' in alldirs or 'packed-refs' in f)):
-                continue
-            for loc in alldirs:
-                if REMOVED_REPO_PAT.match(loc):
-                    to_remove.append([os.path.join(dn, loc),
-                                      self._extract_date(loc)])
-                else:
-                    dirs.append(loc)
-
-        #filter older than (if present)!
-        now = datetime.datetime.now()
-        older_than = self.options.older_than
-        if older_than:
-            to_remove_filtered = []
-            older_than_date = self._parse_older_than(older_than)
-            for name, date_ in to_remove:
-                repo_age = now - date_
-                if repo_age > older_than_date:
-                    to_remove_filtered.append([name, date_])
-
-            to_remove = to_remove_filtered
-            print >> sys.stdout, 'removing %s deleted repos older than %s (%s)' \
-                % (len(to_remove), older_than, older_than_date)
-        else:
-            print >> sys.stdout, 'removing all [%s] deleted repos' \
-                % len(to_remove)
-        if self.options.dont_ask or not to_remove:
-            # don't ask just remove !
-            remove = True
-        else:
-            remove = ask_ok('the following repositories will be deleted completely:\n%s\n'
-                            'are you sure you want to remove them [y/n]?'
-                            % ', \n'.join(['%s removed on %s'
-                    % (safe_str(x[0]), safe_str(x[1])) for x in to_remove]))
-
-        if remove:
-            for path, date_ in to_remove:
-                print >> sys.stdout, 'removing repository %s' % path
-                shutil.rmtree(path)
-        else:
-            print 'nothing done exiting...'
-            sys.exit(0)
-
-    def update_parser(self):
-        self.parser.add_option(
-            '--older-than',
-            action='store',
-            dest='older_than',
-            help=("only remove repos that have been removed "
-                 "at least given time ago. "
-                 "The default is to remove all removed repositories. "
-                 "Possible suffixes: "
-                 "d (days), h (hours), m (minutes), s (seconds). "
-                 "For example --older-than=30d deletes repositories "
-                 "removed more than 30 days ago.")
-            )
-
-        self.parser.add_option(
-            '--dont-ask',
-            action="store_true",
-            dest="dont_ask",
-            help="remove repositories without asking for confirmation."
-        )
--- a/rhodecode/lib/paster_commands/ishell.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/paster_commands/ishell.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.paster_commands.ishell
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    interactive shell paster command for RhodeCode
-
-
-    :created_on: Apr 4, 2013
-    :author: marcink
-    :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.paster_commands.ishell
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+interactive shell paster command for RhodeCode
+
+:created_on: Apr 4, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 from __future__ import with_statement
 
 import os
--- a/rhodecode/lib/paster_commands/make_index.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/paster_commands/make_index.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.paster_commands.make_index
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    make-index paster command for RhodeCode
-
-    :created_on: Aug 17, 2010
-    :author: marcink
-    :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,19 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.paster_commands.make_index
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+make-index paster command for RhodeCode
+
+:created_on: Aug 17, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+"""
+
 from __future__ import with_statement
 
 import os
--- a/rhodecode/lib/paster_commands/make_rcextensions.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/paster_commands/make_rcextensions.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.paster_commands.make_rcextensions
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    make-rcext paster command for RhodeCode
-
-    :created_on: Mar 6, 2012
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,19 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.paster_commands.make_rcextensions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+make-rcext paster command for RhodeCode
+
+:created_on: Mar 6, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+"""
+
 from __future__ import with_statement
 
 import os
--- a/rhodecode/lib/paster_commands/repo_scan.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/paster_commands/repo_scan.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.paster_commands.make_rcextensions
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    repo-scan paster command for RhodeCode
-
-
-    :created_on: Feb 9, 2013
-    :author: marcink
-    :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.paster_commands.make_rcextensions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+repo-scan paster command for RhodeCode
+
+:created_on: Feb 9, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 from __future__ import with_statement
 
 import os
--- a/rhodecode/lib/paster_commands/update_repoinfo.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/paster_commands/update_repoinfo.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.paster_commands.make_rcextensions
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    uodate-repoinfo paster command for RhodeCode
-
-    :created_on: Jul 14, 2012
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.paster_commands.make_rcextensions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+uodate-repoinfo paster command for RhodeCode
+
+:created_on: Jul 14, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 from __future__ import with_statement
 
 import os
--- a/rhodecode/lib/pidlock.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/pidlock.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,9 +1,21 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import with_statement
 import os
-import sys
-import time
 import errno
 
-from warnings import warn
 from multiprocessing.util import Finalize
 
 from rhodecode.lib.compat import kill
@@ -27,9 +39,8 @@
     def __init__(self, file_=None, callbackfn=None,
                  desc='daemon lock', debug=False):
 
-        self.pidfile = file_ if file_ else os.path.join(
-                                                    os.path.dirname(__file__),
-                                                    'running.lock')
+        lock_name = os.path.join(os.path.dirname(__file__), 'running.lock')
+        self.pidfile = file_ if file_ else lock_name
         self.callbackfn = callbackfn
         self.desc = desc
         self.debug = debug
@@ -37,7 +48,7 @@
         #run the lock automatically !
         self.lock()
         self._finalize = Finalize(self, DaemonLock._on_finalize,
-                                    args=(self, debug), exitpriority=10)
+                                  args=(self, debug), exitpriority=10)
 
     @staticmethod
     def _on_finalize(lock, debug):
@@ -63,15 +74,15 @@
         if self.debug:
             print 'checking for already running process'
         try:
-            pidfile = open(self.pidfile, "r")
-            pidfile.seek(0)
-            running_pid = int(pidfile.readline())
-
-            pidfile.close()
+            with open(self.pidfile, 'r') as f:
+                try:
+                    running_pid = int(f.readline())
+                except ValueError:
+                    running_pid = -1
 
             if self.debug:
                 print ('lock file present running_pid: %s, '
-                       'checking for execution') % running_pid
+                       'checking for execution' % (running_pid,))
             # Now we check the PID from lock file matches to the current
             # process PID
             if running_pid:
@@ -128,7 +139,6 @@
         dir_, file_ = os.path.split(pidfile)
         if not os.path.isdir(dir_):
             os.makedirs(dir_)
-        pidfile = open(self.pidfile, "wb")
-        pidfile.write(lockname)
-        pidfile.close
+        with open(self.pidfile, 'wb') as f:
+            f.write(lockname)
         self.held = True
--- a/rhodecode/lib/rcmail/smtp_mailer.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/rcmail/smtp_mailer.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,14 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.rcmail.smtp_mailer
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Simple smtp mailer used in RhodeCode
-
-    :created_on: Sep 13, 2010
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -21,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.rcmail.smtp_mailer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Simple smtp mailer used in RhodeCode
+
+:created_on: Sep 13, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 import time
 import logging
 import smtplib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/recaptcha.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+import urllib
+import urllib2
+
+API_SSL_SERVER = "https://www.google.com/recaptcha/api"
+API_SERVER = "http://www.google.com/recaptcha/api"
+VERIFY_SERVER = "www.google.com"
+
+
+class RecaptchaResponse(object):
+    def __init__(self, is_valid, error_code=None):
+        self.is_valid = is_valid
+        self.error_code = error_code
+
+    def __repr__(self):
+        return '<RecaptchaResponse:%s>' % (self.is_valid)
+
+
+def displayhtml(public_key, use_ssl=False, error=None):
+    """Gets the HTML to display for reCAPTCHA
+
+    public_key -- The public api key
+    use_ssl -- Should the request be sent over ssl?
+    error -- An error message to display (from RecaptchaResponse.error_code)"""
+
+    error_param = ''
+    if error:
+        error_param = '&error=%s' % error
+
+    if use_ssl:
+        server = API_SSL_SERVER
+    else:
+        server = API_SERVER
+
+    return """<script type="text/javascript" src="%(ApiServer)s/challenge?k=%(PublicKey)s%(ErrorParam)s"></script>
+
+<noscript>
+  <iframe src="%(ApiServer)s/noscript?k=%(PublicKey)s%(ErrorParam)s" height="300" width="500" frameborder="0"></iframe><br />
+  <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
+  <input type='hidden' name='recaptcha_response_field' value='manual_challenge' />
+</noscript>
+""" % {
+        'ApiServer': server,
+        'PublicKey': public_key,
+        'ErrorParam': error_param,
+    }
+
+
+def submit(recaptcha_challenge_field, recaptcha_response_field, private_key,
+           remoteip):
+    """
+    Submits a reCAPTCHA request for verification. Returns RecaptchaResponse
+    for the request
+
+    recaptcha_challenge_field -- The value of recaptcha_challenge_field from the form
+    recaptcha_response_field -- The value of recaptcha_response_field from the form
+    private_key -- your reCAPTCHA private key
+    remoteip -- the user's ip address
+    """
+
+    if not (recaptcha_response_field and recaptcha_challenge_field and
+                len(recaptcha_response_field) and len(
+            recaptcha_challenge_field)):
+        return RecaptchaResponse(is_valid=False,
+                                 error_code='incorrect-captcha-sol')
+
+    def encode_if_necessary(s):
+        if isinstance(s, unicode):
+            return s.encode('utf-8')
+        return s
+
+    params = urllib.urlencode({
+        'privatekey': encode_if_necessary(private_key),
+        'remoteip': encode_if_necessary(remoteip),
+        'challenge': encode_if_necessary(recaptcha_challenge_field),
+        'response': encode_if_necessary(recaptcha_response_field),
+    })
+
+    request = urllib2.Request(
+        url="http://%s/recaptcha/api/verify" % VERIFY_SERVER,
+        data=params,
+        headers={
+            "Content-type": "application/x-www-form-urlencoded",
+            "User-agent": "reCAPTCHA Python"
+        }
+    )
+
+    httpresp = urllib2.urlopen(request)
+
+    return_values = httpresp.read().splitlines()
+    httpresp.close()
+
+    return_code = return_values[0]
+
+    if return_code == "true":
+        return RecaptchaResponse(is_valid=True)
+    else:
+        return RecaptchaResponse(is_valid=False, error_code=return_values[1])
--- a/rhodecode/lib/timerproxy.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/timerproxy.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,6 +1,21 @@
-from sqlalchemy.interfaces import ConnectionProxy
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# 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 time
 import logging
+from sqlalchemy.interfaces import ConnectionProxy
+
 log = logging.getLogger('timerproxy')
 
 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
--- a/rhodecode/lib/utils.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/utils.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.utils
-    ~~~~~~~~~~~~~~~~~~~
-
-    Utilities library for RhodeCode
-
-    :created_on: Apr 18, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.utils
+~~~~~~~~~~~~~~~~~~~
+
+Utilities library for RhodeCode
+
+:created_on: Apr 18, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import os
 import re
@@ -40,6 +40,7 @@
 from paste.script.command import Command, BadCommand
 
 from webhelpers.text import collapse, remove_formatting, strip_tags
+from beaker.cache import _cache_decorate
 
 from rhodecode.lib.vcs import get_backend
 from rhodecode.lib.vcs.backends.base import BaseChangeset
@@ -54,10 +55,10 @@
 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
     UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation, UserGroup
 from rhodecode.model.meta import Session
-from rhodecode.model.repos_group import ReposGroupModel
+from rhodecode.model.repo_group import RepoGroupModel
 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_current_rhodecode_user
 from rhodecode.lib.vcs.utils.fakemod import create_module
-from rhodecode.model.users_group import UserGroupModel
+from rhodecode.model.user_group import UserGroupModel
 
 log = logging.getLogger(__name__)
 
@@ -110,7 +111,7 @@
     return _repo
 
 
-def get_repos_group_slug(request):
+def get_repo_group_slug(request):
     _group = request.environ['pylons.routes_dict'].get('group_name')
     if _group:
         _group = _group.rstrip('/')
@@ -131,6 +132,32 @@
     return _group
 
 
+def _extract_id_from_repo_name(repo_name):
+    if repo_name.startswith('/'):
+        repo_name = repo_name.lstrip('/')
+    by_id_match = re.match(r'^_(\d{1,})', repo_name)
+    if by_id_match:
+        return by_id_match.groups()[0]
+
+
+def get_repo_by_id(repo_name):
+    """
+    Extracts repo_name by id from special urls. Example url is _11/repo_name
+
+    :param repo_name:
+    :return: repo_name if matched else None
+    """
+    try:
+        _repo_id = _extract_id_from_repo_name(repo_name)
+        if _repo_id:
+            from rhodecode.model.db import Repository
+            return Repository.get(_repo_id).repo_name
+    except Exception:
+        log.debug('Failed to extract repo_name from URL %s' % (
+                  traceback.format_exc()))
+        return
+
+
 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
     """
     Action logger for various actions made by users
@@ -154,14 +181,14 @@
         ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
 
     try:
-        if hasattr(user, 'user_id'):
+        if getattr(user, 'user_id', None):
             user_obj = User.get(user.user_id)
         elif isinstance(user, basestring):
             user_obj = User.get_by_username(user)
         else:
             raise Exception('You have to provide a user object or a username')
 
-        if hasattr(repo, 'repo_id'):
+        if getattr(repo, 'repo_id', None):
             repo_obj = Repository.get(repo.repo_id)
             repo_name = repo_obj.repo_name
         elif isinstance(repo, basestring):
@@ -261,17 +288,17 @@
         return False
 
 
-def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
+def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
     """
     Returns True if given path is a repository group False otherwise
 
     :param repo_name:
     :param base_path:
     """
-    full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
+    full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
 
     # check if it's not a repo
-    if is_valid_repo(repos_group_name, base_path):
+    if is_valid_repo(repo_group_name, base_path):
         return False
 
     try:
@@ -414,7 +441,7 @@
 
     # last element is repo in nested groups structure
     groups = groups[:-1]
-    rgm = ReposGroupModel(sa)
+    rgm = RepoGroupModel(sa)
     owner = User.get_first_admin()
     for lvl, group_name in enumerate(groups):
         group_name = '/'.join(groups[:lvl] + [group_name])
@@ -455,7 +482,7 @@
     from rhodecode.model.repo import RepoModel
     from rhodecode.model.scm import ScmModel
     sa = meta.Session()
-    rm = RepoModel()
+    repo_model = RepoModel()
     user = User.get_first_admin()
     added = []
 
@@ -468,7 +495,8 @@
 
     for name, repo in initial_repo_list.items():
         group = map_groups(name)
-        db_repo = rm.get_by_repo_name(name)
+        unicode_name = safe_unicode(name)
+        db_repo = repo_model.get_by_repo_name(unicode_name)
         # found repo that is on filesystem not in RhodeCode database
         if not db_repo:
             log.info('repository %s not found, creating now' % name)
@@ -477,28 +505,32 @@
                     if repo.description != 'unknown'
                     else '%s repository' % name)
 
-            new_repo = rm.create_repo(
+            new_repo = repo_model._create_repo(
                 repo_name=name,
                 repo_type=repo.alias,
                 description=desc,
-                repos_group=getattr(group, 'group_id', None),
+                repo_group=getattr(group, 'group_id', None),
                 owner=user,
-                just_db=True,
                 enable_locking=enable_locking,
                 enable_downloads=enable_downloads,
                 enable_statistics=enable_statistics,
-                private=private
+                private=private,
+                state=Repository.STATE_CREATED
             )
+            sa.commit()
             # we added that repo just now, and make sure it has githook
-            # installed
+            # installed, and updated server info
             if new_repo.repo_type == 'git':
-                ScmModel().install_git_hook(new_repo.scm_instance)
+                git_repo = new_repo.scm_instance
+                ScmModel().install_git_hook(git_repo)
+                # update repository server-info
+                log.debug('Running update server info')
+                git_repo._update_server_info()
             new_repo.update_changeset_cache()
         elif install_git_hook:
             if db_repo.repo_type == 'git':
                 ScmModel().install_git_hook(db_repo.scm_instance)
 
-    sa.commit()
     removed = []
     if remove_obsolete:
         # remove from database those repositories that are not in the filesystem
@@ -570,10 +602,10 @@
 
         # auto check if the module is not missing any data, set to default if is
         # this will help autoupdate new feature of rcext module
-        from rhodecode.config import rcextensions
-        for k in dir(rcextensions):
-            if not k.startswith('_') and not hasattr(EXT, k):
-                setattr(EXT, k, getattr(rcextensions, k))
+        #from rhodecode.config import rcextensions
+        #for k in dir(rcextensions):
+        #    if not k.startswith('_') and not hasattr(EXT, k):
+        #        setattr(EXT, k, getattr(rcextensions, k))
 
 
 def get_custom_lexer(extension):
@@ -639,6 +671,7 @@
     dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
                         tests=True)
     dbmanage.create_tables(override=True)
+    # for tests dynamically set new root paths based on generated content
     dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
     dbmanage.create_default_user()
     dbmanage.admin_prompt()
@@ -819,3 +852,30 @@
         log.warning(msg)
     log.debug("Returning JSON wrapped action output")
     return json.dumps(data, encoding='utf-8')
+
+
+def conditional_cache(region, prefix, condition, func):
+    """
+
+    Conditional caching function use like::
+        def _c(arg):
+            #heavy computation function
+            return data
+
+        # denpending from condition the compute is wrapped in cache or not
+        compute = conditional_cache('short_term', 'cache_desc', codnition=True, func=func)
+        return compute(arg)
+
+    :param region: name of cache region
+    :param prefix: cache region prefix
+    :param condition: condition for cache to be triggered, and return data cached
+    :param func: wrapped heavy function to compute
+
+    """
+    wrapped = func
+    if condition:
+        log.debug('conditional_cache: True, wrapping call of '
+                  'func: %s into %s region cache' % (region, func))
+        wrapped = _cache_decorate((prefix,), None, None, region)(func)
+
+    return wrapped
--- a/rhodecode/lib/utils2.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/utils2.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.utils
-    ~~~~~~~~~~~~~~~~~~~
-
-    Some simple helper functions
-
-    :created_on: Jan 5, 2011
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.lib.utils
+~~~~~~~~~~~~~~~~~~~
+
+Some simple helper functions
+
+:created_on: Jan 5, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import os
 import re
@@ -31,6 +32,8 @@
 import datetime
 import traceback
 import webob
+import urllib
+import urlobject
 
 from pylons.i18n.translation import _, ungettext
 from rhodecode.lib.vcs.utils.lazy import LazyProperty
@@ -488,6 +491,30 @@
     return ''.join(uri)
 
 
+def get_clone_url(uri_tmpl, qualifed_home_url, repo_name, repo_id, **override):
+    parsed_url = urlobject.URLObject(qualifed_home_url)
+    decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
+    args = {
+        'scheme': parsed_url.scheme,
+        'user': '',
+        'netloc': parsed_url.netloc+decoded_path,  # path if we use proxy-prefix
+        'prefix': decoded_path,
+        'repo': repo_name,
+        'repoid': str(repo_id)
+    }
+    args.update(override)
+    args['user'] = urllib.quote(safe_str(args['user']))
+
+    for k, v in args.items():
+        uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
+
+    # remove leading @ sign if it's present. Case of empty user
+    url_obj = urlobject.URLObject(uri_tmpl)
+    url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
+
+    return safe_unicode(url)
+
+
 def get_changeset_safe(repo, rev):
     """
     Safe version of get_changeset if this changeset doesn't exists for a
@@ -505,7 +532,7 @@
 
     try:
         cs = repo.get_changeset(rev)
-    except RepositoryError:
+    except (RepositoryError, LookupError):
         cs = EmptyChangeset(requested_revision=rev)
     return cs
 
@@ -643,6 +670,7 @@
         unique_id = int(unique_id / alphabet_length)
     return "".join(output)[:truncate_to]
 
+
 def get_current_rhodecode_user():
     """
     Gets rhodecode user from threadlocal tmpl_context variable if it's
@@ -653,3 +681,71 @@
         return tmpl_context.rhodecode_user
 
     return None
+
+
+class OptionalAttr(object):
+    """
+    Special Optional Option that defines other attribute. Example::
+
+        def test(apiuser, userid=Optional(OAttr('apiuser')):
+            user = Optional.extract(userid)
+            # calls
+
+    """
+
+    def __init__(self, attr_name):
+        self.attr_name = attr_name
+
+    def __repr__(self):
+        return '<OptionalAttr:%s>' % self.attr_name
+
+    def __call__(self):
+        return self
+
+#alias
+OAttr = OptionalAttr
+
+
+class Optional(object):
+    """
+    Defines an optional parameter::
+
+        param = param.getval() if isinstance(param, Optional) else param
+        param = param() if isinstance(param, Optional) else param
+
+    is equivalent of::
+
+        param = Optional.extract(param)
+
+    """
+
+    def __init__(self, type_):
+        self.type_ = type_
+
+    def __repr__(self):
+        return '<Optional:%s>' % self.type_.__repr__()
+
+    def __call__(self):
+        return self.getval()
+
+    def getval(self):
+        """
+        returns value from this Optional instance
+        """
+        if isinstance(self.type_, OAttr):
+            # use params name
+            return self.type_.attr_name
+        return self.type_
+
+    @classmethod
+    def extract(cls, val):
+        """
+        Extracts value from Optional() instance
+
+        :param val:
+        :return: original value if it's not Optional instance else
+            value of instance
+        """
+        if isinstance(val, cls):
+            return val.getval()
+        return val
--- a/rhodecode/lib/vcs/backends/base.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/vcs/backends/base.py	Wed Jul 02 19:03:13 2014 -0400
@@ -12,7 +12,7 @@
 import datetime
 import itertools
 
-from rhodecode.lib.vcs.utils import author_name, author_email
+from rhodecode.lib.vcs.utils import author_name, author_email, safe_unicode
 from rhodecode.lib.vcs.utils.lazy import LazyProperty
 from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs
 from rhodecode.lib.vcs.conf import settings
@@ -99,6 +99,10 @@
     def name(self):
         raise NotImplementedError
 
+    @property
+    def name_unicode(self):
+        return safe_unicode(self.name)
+
     @LazyProperty
     def owner(self):
         raise NotImplementedError
@@ -133,6 +137,9 @@
         """
         raise NotImplementedError
 
+    def is_empty(self):
+        return self._empty
+
     def get_last_change(self):
         self.get_changesets()
 
@@ -905,6 +912,7 @@
                 try:
                     old = p.get_node(node.path)
                     missing.remove(node)
+                    # if content actually changed, remove node from unchanged
                     if old.content != node.content:
                         not_changed.remove(node)
                 except NodeDoesNotExistError:
@@ -1027,4 +1035,4 @@
         return CollectionGenerator(self.repo, sliced_revs)
 
     def __repr__(self):
-        return 'CollectionGenerator<%s>' % (len(self))
+        return '<CollectionGenerator[len:%s]>' % (len(self))
--- a/rhodecode/lib/vcs/backends/git/changeset.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/vcs/backends/git/changeset.py	Wed Jul 02 19:03:13 2014 -0400
@@ -189,13 +189,15 @@
         """
         rev_filter = _git_path = settings.GIT_REV_FILTER
         so, se = self.repository.run_git_command(
-            "rev-list %s --children | grep '^%s'" % (rev_filter, self.raw_id)
+            "rev-list %s --children" % (rev_filter)
         )
 
         children = []
+        pat = re.compile(r'^%s' % self.raw_id)
         for l in so.splitlines():
-            childs = l.split(' ')[1:]
-            children.extend(childs)
+            if pat.match(l):
+                childs = l.split(' ')[1:]
+                children.extend(childs)
         return [self.repository.get_changeset(cs) for cs in children]
 
     def next(self, branch=None):
@@ -294,17 +296,15 @@
         f_path = safe_str(path)
 
         if limit:
-            cmd = 'log -n %s --pretty="format: %%H" -s -p %s -- "%s"' % (
-                      safe_int(limit, 0), cs_id, f_path
-                   )
+            cmd = 'log -n %s --pretty="format: %%H" -s %s -- "%s"' % (
+                      safe_int(limit, 0), cs_id, f_path)
 
         else:
-            cmd = 'log --pretty="format: %%H" -s -p %s -- "%s"' % (
-                      cs_id, f_path
-                   )
+            cmd = 'log --pretty="format: %%H" -s %s -- "%s"' % (
+                      cs_id, f_path)
         so, se = self.repository.run_git_command(cmd)
         ids = re.findall(r'[0-9a-fA-F]{40}', so)
-        return [self.repository.get_changeset(id) for id in ids]
+        return [self.repository.get_changeset(sha) for sha in ids]
 
     def get_file_history_2(self, path):
         """
@@ -385,7 +385,7 @@
             raise VCSError('You need to pass in a valid stream for filling'
                            ' with archival data')
         popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
-            cwd=self.repository.path)
+                      cwd=self.repository.path)
 
         buffer_size = 1024 * 8
         chunk = popen.stdout.read(buffer_size)
--- a/rhodecode/lib/vcs/backends/git/config.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,345 +0,0 @@
-# config.py - Reading and writing Git config files
-# Copyright (C) 2011 Jelmer Vernooij <jelmer@samba.org>
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; version 2
-# of the License or (at your option) a later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
-# MA  02110-1301, USA.
-
-"""Reading and writing Git configuration files.
-
-TODO:
- * preserve formatting when updating configuration files
- * treat subsection names as case-insensitive for [branch.foo] style
-   subsections
-"""
-
-# Taken from dulwich not yet released 0.8.3 version (until it is actually
-# released)
-
-import errno
-import os
-import re
-
-from dulwich.file import GitFile
-
-
-class Config(object):
-    """A Git configuration."""
-
-    def get(self, section, name):
-        """Retrieve the contents of a configuration setting.
-
-        :param section: Tuple with section name and optional subsection namee
-        :param subsection: Subsection name
-        :return: Contents of the setting
-        :raise KeyError: if the value is not set
-        """
-        raise NotImplementedError(self.get)
-
-    def get_boolean(self, section, name, default=None):
-        """Retrieve a configuration setting as boolean.
-
-        :param section: Tuple with section name and optional subsection namee
-        :param name: Name of the setting, including section and possible
-            subsection.
-        :return: Contents of the setting
-        :raise KeyError: if the value is not set
-        """
-        try:
-            value = self.get(section, name)
-        except KeyError:
-            return default
-        if value.lower() == "true":
-            return True
-        elif value.lower() == "false":
-            return False
-        raise ValueError("not a valid boolean string: %r" % value)
-
-    def set(self, section, name, value):
-        """Set a configuration value.
-
-        :param name: Name of the configuration value, including section
-            and optional subsection
-        :param: Value of the setting
-        """
-        raise NotImplementedError(self.set)
-
-
-class ConfigDict(Config):
-    """Git configuration stored in a dictionary."""
-
-    def __init__(self, values=None):
-        """Create a new ConfigDict."""
-        if values is None:
-            values = {}
-        self._values = values
-
-    def __repr__(self):
-        return "%s(%r)" % (self.__class__.__name__, self._values)
-
-    def __eq__(self, other):
-        return (
-            isinstance(other, self.__class__) and
-            other._values == self._values)
-
-    @classmethod
-    def _parse_setting(cls, name):
-        parts = name.split(".")
-        if len(parts) == 3:
-            return (parts[0], parts[1], parts[2])
-        else:
-            return (parts[0], None, parts[1])
-
-    def get(self, section, name):
-        if isinstance(section, basestring):
-            section = (section, )
-        if len(section) > 1:
-            try:
-                return self._values[section][name]
-            except KeyError:
-                pass
-        return self._values[(section[0],)][name]
-
-    def set(self, section, name, value):
-        if isinstance(section, basestring):
-            section = (section, )
-        self._values.setdefault(section, {})[name] = value
-
-
-def _format_string(value):
-    if (value.startswith(" ") or
-        value.startswith("\t") or
-        value.endswith(" ") or
-        value.endswith("\t")):
-        return '"%s"' % _escape_value(value)
-    return _escape_value(value)
-
-
-def _parse_string(value):
-    value = value.strip()
-    ret = []
-    block = []
-    in_quotes = False
-    for c in value:
-        if c == "\"":
-            in_quotes = (not in_quotes)
-            ret.append(_unescape_value("".join(block)))
-            block = []
-        elif c in ("#", ";") and not in_quotes:
-            # the rest of the line is a comment
-            break
-        else:
-            block.append(c)
-
-    if in_quotes:
-        raise ValueError("value starts with quote but lacks end quote")
-
-    ret.append(_unescape_value("".join(block)).rstrip())
-
-    return "".join(ret)
-
-
-def _unescape_value(value):
-    """Unescape a value."""
-    def unescape(c):
-        return {
-            "\\\\": "\\",
-            "\\\"": "\"",
-            "\\n": "\n",
-            "\\t": "\t",
-            "\\b": "\b",
-            }[c.group(0)]
-    return re.sub(r"(\\.)", unescape, value)
-
-
-def _escape_value(value):
-    """Escape a value."""
-    return value.replace("\\", "\\\\").replace("\n", "\\n")\
-            .replace("\t", "\\t").replace("\"", "\\\"")
-
-
-def _check_variable_name(name):
-    for c in name:
-        if not c.isalnum() and c != '-':
-            return False
-    return True
-
-
-def _check_section_name(name):
-    for c in name:
-        if not c.isalnum() and c not in ('-', '.'):
-            return False
-    return True
-
-
-def _strip_comments(line):
-    line = line.split("#")[0]
-    line = line.split(";")[0]
-    return line
-
-
-class ConfigFile(ConfigDict):
-    """A Git configuration file, like .git/config or ~/.gitconfig.
-    """
-
-    @classmethod
-    def from_file(cls, f):
-        """Read configuration from a file-like object."""
-        ret = cls()
-        section = None
-        setting = None
-        for lineno, line in enumerate(f.readlines()):
-            line = line.lstrip()
-            if setting is None:
-                if _strip_comments(line).strip() == "":
-                    continue
-                if line[0] == "[":
-                    line = _strip_comments(line).rstrip()
-                    if line[-1] != "]":
-                        raise ValueError("expected trailing ]")
-                    key = line.strip()
-                    pts = key[1:-1].split(" ", 1)
-                    pts[0] = pts[0].lower()
-                    if len(pts) == 2:
-                        if pts[1][0] != "\"" or pts[1][-1] != "\"":
-                            raise ValueError(
-                                "Invalid subsection " + pts[1])
-                        else:
-                            pts[1] = pts[1][1:-1]
-                        if not _check_section_name(pts[0]):
-                            raise ValueError("invalid section name %s" %
-                                             pts[0])
-                        section = (pts[0], pts[1])
-                    else:
-                        if not _check_section_name(pts[0]):
-                            raise ValueError("invalid section name %s" %
-                                    pts[0])
-                        pts = pts[0].split(".", 1)
-                        if len(pts) == 2:
-                            section = (pts[0], pts[1])
-                        else:
-                            section = (pts[0], )
-                    ret._values[section] = {}
-                else:
-                    if section is None:
-                        raise ValueError("setting %r without section" % line)
-                    try:
-                        setting, value = line.split("=", 1)
-                    except ValueError:
-                        setting = line
-                        value = "true"
-                    setting = setting.strip().lower()
-                    if not _check_variable_name(setting):
-                        raise ValueError("invalid variable name %s" % setting)
-                    if value.endswith("\\\n"):
-                        value = value[:-2]
-                        continuation = True
-                    else:
-                        continuation = False
-                    value = _parse_string(value)
-                    ret._values[section][setting] = value
-                    if not continuation:
-                        setting = None
-            else:  # continuation line
-                if line.endswith("\\\n"):
-                    line = line[:-2]
-                    continuation = True
-                else:
-                    continuation = False
-                value = _parse_string(line)
-                ret._values[section][setting] += value
-                if not continuation:
-                    setting = None
-        return ret
-
-    @classmethod
-    def from_path(cls, path):
-        """Read configuration from a file on disk."""
-        f = GitFile(path, 'rb')
-        try:
-            ret = cls.from_file(f)
-            ret.path = path
-            return ret
-        finally:
-            f.close()
-
-    def write_to_path(self, path=None):
-        """Write configuration to a file on disk."""
-        if path is None:
-            path = self.path
-        f = GitFile(path, 'wb')
-        try:
-            self.write_to_file(f)
-        finally:
-            f.close()
-
-    def write_to_file(self, f):
-        """Write configuration to a file-like object."""
-        for section, values in self._values.iteritems():
-            try:
-                section_name, subsection_name = section
-            except ValueError:
-                (section_name, ) = section
-                subsection_name = None
-            if subsection_name is None:
-                f.write("[%s]\n" % section_name)
-            else:
-                f.write("[%s \"%s\"]\n" % (section_name, subsection_name))
-            for key, value in values.iteritems():
-                f.write("%s = %s\n" % (key, _escape_value(value)))
-
-
-class StackedConfig(Config):
-    """Configuration which reads from multiple config files.."""
-
-    def __init__(self, backends, writable=None):
-        self.backends = backends
-        self.writable = writable
-
-    def __repr__(self):
-        return "<%s for %r>" % (self.__class__.__name__, self.backends)
-
-    @classmethod
-    def default_backends(cls):
-        """Retrieve the default configuration.
-
-        This will look in the repository configuration (if for_path is
-        specified), the users' home directory and the system
-        configuration.
-        """
-        paths = [os.path.expanduser("~/.gitconfig"), "/etc/gitconfig"]
-        backends = []
-        for path in paths:
-            try:
-                cf = ConfigFile.from_path(path)
-            except (IOError, OSError), e:
-                if e.errno != errno.ENOENT:
-                    raise
-                else:
-                    continue
-            backends.append(cf)
-        return backends
-
-    def get(self, section, name):
-        for backend in self.backends:
-            try:
-                return backend.get(section, name)
-            except KeyError:
-                pass
-        raise KeyError(name)
-
-    def set(self, section, name, value):
-        if self.writable is None:
-            raise NotImplementedError(self.set)
-        return self.writable.set(section, name, value)
--- a/rhodecode/lib/vcs/backends/git/inmemory.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/vcs/backends/git/inmemory.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,6 +1,7 @@
 import time
 import datetime
 import posixpath
+import stat
 from dulwich import objects
 from dulwich.repo import Repo
 from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset
@@ -38,7 +39,6 @@
         object_store = repo.object_store
 
         ENCODING = "UTF-8"
-        DIRMOD = 040000
 
         # Create tree and populates it with blobs
         commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or\
@@ -83,11 +83,11 @@
                 new_trees.append(curtree)
                 for dirname in reversed_dirnames[:-1]:
                     newtree = objects.Tree()
-                    #newtree.add(DIRMOD, dirname, curtree.id)
-                    newtree[dirname] = DIRMOD, curtree.id
+                    #newtree.add(stat.S_IFDIR, dirname, curtree.id)
+                    newtree[dirname] = stat.S_IFDIR, curtree.id
                     new_trees.append(newtree)
                     curtree = newtree
-                parent[reversed_dirnames[-1]] = DIRMOD, curtree.id
+                parent[reversed_dirnames[-1]] = stat.S_IFDIR, curtree.id
             else:
                 parent.add(name=node_path, mode=node.mode, hexsha=blob.id)
 
@@ -95,7 +95,7 @@
             # Update ancestors
             for parent, tree, path in reversed([(a[1], b[1], b[0]) for a, b in
                 zip(ancestors, ancestors[1:])]):
-                parent[path] = DIRMOD, tree.id
+                parent[path] = stat.S_IFDIR, tree.id
                 object_store.add_object(tree)
 
             object_store.add_object(blob)
@@ -192,8 +192,7 @@
             tree = get_tree_for_dir(parent, dirname)
             if tree is None:
                 tree = objects.Tree()
-                dirmode = 040000
-                parent.add(dirmode, dirname, tree.id)
+                parent.add(stat.S_IFDIR, dirname, tree.id)
                 parent = tree
             # Always append tree
             trees.append(tree)
--- a/rhodecode/lib/vcs/backends/git/repository.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/vcs/backends/git/repository.py	Wed Jul 02 19:03:13 2014 -0400
@@ -20,6 +20,7 @@
 
 from dulwich.objects import Tag
 from dulwich.repo import Repo, NotGitRepository
+from dulwich.config import ConfigFile
 
 from rhodecode.lib.vcs import subprocessio
 from rhodecode.lib.vcs.backends.base import BaseRepository, CollectionGenerator
@@ -39,7 +40,6 @@
 )
 
 from .changeset import GitChangeset
-from .config import ConfigFile
 from .inmemory import GitInMemoryChangeset
 from .workdir import GitWorkdir
 
@@ -165,25 +165,31 @@
     @classmethod
     def _check_url(cls, url):
         """
-        Functon will check given url and try to verify if it's a valid
-        link. Sometimes it may happened that mercurial will issue basic
+        Function will check given url and try to verify if it's a valid
+        link. Sometimes it may happened that git will issue basic
         auth request that can cause whole API to hang when used from python
         or other external calls.
 
-        On failures it'll raise urllib2.HTTPError
+        On failures it'll raise urllib2.HTTPError, exception is also thrown
+        when the return code is non 200
         """
 
         # check first if it's not an local url
         if os.path.isdir(url) or url.startswith('file:'):
             return True
 
-        if('+' in url[:url.find('://')]):
+        if '+' in url[:url.find('://')]:
             url = url[url.find('+') + 1:]
 
         handlers = []
-        test_uri, authinfo = hg_url(url).authinfo()
+        url_obj = hg_url(url)
+        test_uri, authinfo = url_obj.authinfo()
+        url_obj.passwd = '*****'
+        cleaned_uri = str(url_obj)
+
         if not test_uri.endswith('info/refs'):
             test_uri = test_uri.rstrip('/') + '/info/refs'
+
         if authinfo:
             #create a password manager
             passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
@@ -202,10 +208,19 @@
 
         try:
             resp = o.open(req)
-            return resp.code == 200
+            if resp.code != 200:
+                raise Exception('Return Code is not 200')
         except Exception, e:
             # means it cannot be cloned
-            raise urllib2.URLError("[%s] %s" % (url, e))
+            raise urllib2.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
+
+        # now detect if it's proper git repo
+        gitdata = resp.read()
+        if not 'service=git-upload-pack' in gitdata:
+            raise urllib2.URLError(
+                "url [%s] does not look like an git" % (cleaned_uri))
+
+        return True
 
     def _get_repo(self, create, src_url=None, update_after_clone=False,
                   bare=False):
@@ -220,7 +235,7 @@
                 self.clone(src_url, update_after_clone, bare)
                 return Repo(self.path)
             elif create:
-                os.mkdir(self.path)
+                os.makedirs(self.path)
                 if bare:
                     return Repo.init_bare(self.path)
                 else:
@@ -274,8 +289,8 @@
             try:
                 revision = self.revisions[int(revision)]
             except Exception:
-                raise ChangesetDoesNotExistError("Revision %s does not exist "
-                    "for this repository" % (revision))
+                msg = ("Revision %s does not exist for %s" % (revision, self))
+                raise ChangesetDoesNotExistError(msg)
 
         elif is_bstr:
             # get by branch/tag name
@@ -289,8 +304,8 @@
                 return _tags_shas[_tags_shas.index(revision)]
 
             elif not SHA_PATTERN.match(revision) or revision not in self.revisions:
-                raise ChangesetDoesNotExistError("Revision %s does not exist "
-                    "for this repository" % (revision))
+                msg = ("Revision %s does not exist for %s" % (revision, self))
+                raise ChangesetDoesNotExistError(msg)
 
         # Ensure we return full id
         if not SHA_PATTERN.match(str(revision)):
@@ -348,13 +363,9 @@
 
     @LazyProperty
     def description(self):
-        idx_loc = '' if self.bare else '.git'
         undefined_description = u'unknown'
-        description_path = os.path.join(self.path, idx_loc, 'description')
-        if os.path.isfile(description_path):
-            return safe_unicode(open(description_path).read())
-        else:
-            return undefined_description
+        _desc = self._repo.get_description()
+        return safe_unicode(_desc or undefined_description)
 
     @LazyProperty
     def contact(self):
@@ -631,7 +642,7 @@
           *bare* git repository (no working directory at all).
         """
         url = self._get_url(url)
-        cmd = ['clone']
+        cmd = ['clone', '-q']
         if bare:
             cmd.append('--bare')
         elif not update_after_clone:
@@ -665,6 +676,13 @@
         cmd = '''fetch %s -- %s''' % (url, refs)
         self.run_git_command(cmd)
 
+    def _update_server_info(self):
+        """
+        runs gits update-server-info command in this repo instance
+        """
+        from dulwich.server import update_server_info
+        update_server_info(self._repo)
+
     @LazyProperty
     def workdir(self):
         """
--- a/rhodecode/lib/vcs/backends/hg/repository.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/vcs/backends/hg/repository.py	Wed Jul 02 19:03:13 2014 -0400
@@ -33,7 +33,7 @@
 from rhodecode.lib.vcs.utils.hgcompat import (
     ui, nullid, match, patch, diffopts, clone, get_contact, pull,
     localrepository, RepoLookupError, Abort, RepoError, hex, scmutil, hg_url,
-    httpbasicauthhandler, httpdigestauthhandler, peer
+    httpbasicauthhandler, httpdigestauthhandler, peer, httppeer
 )
 
 from .changeset import MercurialChangeset
@@ -288,26 +288,28 @@
                                         context=context)))
 
     @classmethod
-    def _check_url(cls, url):
+    def _check_url(cls, url, repoui=None):
         """
         Function will check given url and try to verify if it's a valid
         link. Sometimes it may happened that mercurial will issue basic
         auth request that can cause whole API to hang when used from python
         or other external calls.
 
-        On failures it'll raise urllib2.HTTPError, return code 200 if url
-        is valid or True if it's a local path
+        On failures it'll raise urllib2.HTTPError, exception is also thrown
+        when the return code is non 200
         """
-
         # check first if it's not an local url
         if os.path.isdir(url) or url.startswith('file:'):
             return True
 
-        if('+' in url[:url.find('://')]):
+        if '+' in url[:url.find('://')]:
             url = url[url.find('+') + 1:]
 
         handlers = []
-        test_uri, authinfo = hg_url(url).authinfo()
+        url_obj = hg_url(url)
+        test_uri, authinfo = url_obj.authinfo()
+        url_obj.passwd = '*****'
+        cleaned_uri = str(url_obj)
 
         if authinfo:
             #create a password manager
@@ -329,10 +331,21 @@
 
         try:
             resp = o.open(req)
-            return resp.code == 200
+            if resp.code != 200:
+                raise Exception('Return Code is not 200')
         except Exception, e:
             # means it cannot be cloned
-            raise urllib2.URLError("[%s] %s" % (url, e))
+            raise urllib2.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
+
+        # now check if it's a proper hg repo
+        try:
+            repo_id = httppeer(repoui or ui.ui(), url).lookup('tip')
+        except Exception, e:
+            raise urllib2.URLError(
+                "url [%s] does not look like an hg repo org_exc: %s"
+                % (cleaned_uri, e))
+
+        return True
 
     def _get_repo(self, create, src_url=None, update_after_clone=False):
         """
@@ -352,7 +365,7 @@
                 if not update_after_clone:
                     opts.update({'noupdate': True})
                 try:
-                    MercurialRepository._check_url(url)
+                    MercurialRepository._check_url(url, self.baseui)
                     clone(self.baseui, url, self.path, **opts)
 #                except urllib2.URLError:
 #                    raise Abort("Got HTTP 404 error")
@@ -378,8 +391,8 @@
     @LazyProperty
     def description(self):
         undefined_description = u'unknown'
-        return safe_unicode(self._repo.ui.config('web', 'description',
-                                   undefined_description, untrusted=True))
+        _desc = self._repo.ui.config('web', 'description', None, untrusted=True)
+        return safe_unicode(_desc or undefined_description)
 
     @LazyProperty
     def contact(self):
@@ -425,10 +438,13 @@
 
         try:
             revision = hex(self._repo.lookup(revision))
+        except (LookupError, ):
+            msg = ("Ambiguous identifier `%s` for %s" % (revision, self))
+            raise ChangesetDoesNotExistError(msg)
         except (IndexError, ValueError, RepoLookupError, TypeError):
-            raise ChangesetDoesNotExistError("Revision %s does not "
-                                    "exist for this repository"
-                                    % (revision))
+            msg = ("Revision %s does not exist for %s" % (revision, self))
+            raise ChangesetDoesNotExistError(msg)
+
         return revision
 
     def _get_archives(self, archive_name='tip'):
@@ -490,8 +506,8 @@
                                   "after end revision '%s'" % (start, end))
 
         if branch_name and branch_name not in self.allbranches.keys():
-            raise BranchDoesNotExistError('Branch %s not found in'
-                                  ' this repository' % branch_name)
+            msg = ("Branch %s not found in %s" % (branch_name, self))
+            raise BranchDoesNotExistError(msg)
         if end_pos is not None:
             end_pos += 1
         #filter branches
--- a/rhodecode/lib/vcs/nodes.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/vcs/nodes.py	Wed Jul 02 19:03:13 2014 -0400
@@ -326,7 +326,7 @@
                 return self._mimetype
             else:
                 raise NodeError('given _mimetype attribute must be an 2 '
-                               'element list or tuple')
+                                'element list or tuple')
 
         mtype, encoding = mimetypes.guess_type(self.name)
 
@@ -337,6 +337,17 @@
             else:
                 mtype = 'text/plain'
                 encoding = None
+
+                #try with pygments
+                try:
+                    from pygments.lexers import get_lexer_for_filename
+                    mt = get_lexer_for_filename(self.name).mimetypes
+                except Exception:
+                    mt = None
+
+                if mt:
+                    mtype = mt[0]
+
         return mtype, encoding
 
     @LazyProperty
--- a/rhodecode/lib/vcs/subprocessio.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/lib/vcs/subprocessio.py	Wed Jul 02 19:03:13 2014 -0400
@@ -4,7 +4,7 @@
 stream processor exposing the output data as an iterator fitting to be a
 return value passed by a WSGI applicaiton to a WSGI server per PEP 3333.
 
-Copyright (c) 2011  Daniel Dotsenko <dotsa@hotmail.com>
+Copyright (c) 2011  Daniel Dotsenko <dotsa[at]hotmail.com>
 
 This file is part of git_http_backend.py Project.
 
@@ -34,6 +34,7 @@
     without blocking the main thread.
     We close inpipe once the end of the source stream is reached.
     """
+
     def __init__(self, source):
         super(StreamFeeder, self).__init__()
         self.daemon = True
@@ -129,12 +130,13 @@
             if len(t) > ccm:
                 kr.clear()
                 kr.wait(2)
-#                # this only works on 2.7.x and up
-#                if not kr.wait(10):
-#                    raise Exception("Timed out while waiting for input to be read.")
+                # # this only works on 2.7.x and up
+                # if not kr.wait(10):
+                #     raise Exception("Timed out while waiting for input to be read.")
                 # instead we'll use this
                 if len(t) > ccm + 3:
-                    raise IOError("Timed out while waiting for input from subprocess.")
+                    raise IOError(
+                        "Timed out while waiting for input from subprocess.")
             t.append(b)
             da.set()
             b = s.read(cs)
@@ -142,7 +144,7 @@
         da.set()  # for cases when done but there was no input.
 
 
-class BufferedGenerator():
+class BufferedGenerator(object):
     """
     Class behaves as a non-blocking, buffered pipe reader.
     Reads chunks of data (through a thread)
@@ -164,7 +166,6 @@
             maxlen = None
 
         self.data = deque(starting_values, maxlen)
-
         self.worker = InputStreamChunker(source, self.data, buffer_size,
                                          chunk_size)
         if starting_values:
@@ -325,6 +326,7 @@
 
 
     """
+
     def __init__(self, cmd, inputstream=None, buffer_size=65536,
                  chunk_size=4096, starting_values=[], **kwargs):
         """
@@ -347,15 +349,14 @@
             cmd = ' '.join(cmd)
 
         kwargs['shell'] = _shell
-        _p = subprocess.Popen(cmd,
-            bufsize=-1,
-            stdin=inputstream,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.PIPE,
-            **kwargs
-        )
+        _p = subprocess.Popen(cmd, bufsize=-1,
+                              stdin=inputstream,
+                              stdout=subprocess.PIPE,
+                              stderr=subprocess.PIPE,
+                              **kwargs)
 
-        bg_out = BufferedGenerator(_p.stdout, buffer_size, chunk_size, starting_values)
+        bg_out = BufferedGenerator(_p.stdout, buffer_size, chunk_size,
+                                   starting_values)
         bg_err = BufferedGenerator(_p.stderr, 16000, 1, bottomless=True)
 
         while not bg_out.done_reading and not bg_out.reading_paused and not bg_err.length:
@@ -371,14 +372,16 @@
         if _returncode or (_returncode is None and bg_err.length):
             try:
                 _p.terminate()
-            except:
+            except Exception:
                 pass
             bg_out.stop()
             bg_err.stop()
             err = '%s' % ''.join(bg_err)
             if err:
-                raise EnvironmentError("Subprocess exited due to an error:\n" + err)
-            raise EnvironmentError("Subprocess exited with non 0 ret code:%s" % _returncode)
+                raise EnvironmentError(
+                    "Subprocess exited due to an error:\n" + err)
+            raise EnvironmentError(
+                "Subprocess exited with non 0 ret code:%s" % _returncode)
 
         self.process = _p
         self.output = bg_out
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/verlib.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,326 @@
+"""
+"Rational" version definition and parsing for DistutilsVersionFight
+discussion at PyCon 2009.
+"""
+
+import sys
+import re
+
+class IrrationalVersionError(Exception):
+    """This is an irrational version."""
+    pass
+
+class HugeMajorVersionNumError(IrrationalVersionError):
+    """An irrational version because the major version number is huge
+    (often because a year or date was used).
+
+    See `error_on_huge_major_num` option in `NormalizedVersion` for details.
+    This guard can be disabled by setting that option False.
+    """
+    pass
+
+# A marker used in the second and third parts of the `parts` tuple, for
+# versions that don't have those segments, to sort properly. An example
+# of versions in sort order ('highest' last):
+#   1.0b1                 ((1,0), ('b',1), ('f',))
+#   1.0.dev345            ((1,0), ('f',),  ('dev', 345))
+#   1.0                   ((1,0), ('f',),  ('f',))
+#   1.0.post256.dev345    ((1,0), ('f',),  ('f', 'post', 256, 'dev', 345))
+#   1.0.post345           ((1,0), ('f',),  ('f', 'post', 345, 'f'))
+#                                   ^        ^                 ^
+#   'b' < 'f' ---------------------/         |                 |
+#                                            |                 |
+#   'dev' < 'f' < 'post' -------------------/                  |
+#                                                              |
+#   'dev' < 'f' ----------------------------------------------/
+# Other letters would do, but 'f' for 'final' is kind of nice.
+FINAL_MARKER = ('f',)
+
+VERSION_RE = re.compile(r'''
+    ^
+    (?P<version>\d+\.\d+)          # minimum 'N.N'
+    (?P<extraversion>(?:\.\d+)*)   # any number of extra '.N' segments
+    (?:
+        (?P<prerel>[abc]|rc)       # 'a'=alpha, 'b'=beta, 'c'=release candidate
+                                   # 'rc'= alias for release candidate
+        (?P<prerelversion>\d+(?:\.\d+)*)
+    )?
+    (?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?
+    $''', re.VERBOSE)
+
+class NormalizedVersion(object):
+    """A rational version.
+
+    Good:
+        1.2         # equivalent to "1.2.0"
+        1.2.0
+        1.2a1
+        1.2.3a2
+        1.2.3b1
+        1.2.3c1
+        1.2.3.4
+        TODO: fill this out
+
+    Bad:
+        1           # mininum two numbers
+        1.2a        # release level must have a release serial
+        1.2.3b
+    """
+    def __init__(self, s, error_on_huge_major_num=True):
+        """Create a NormalizedVersion instance from a version string.
+
+        :param s {str} The version string.
+        :param error_on_huge_major_num {bool} Whether to consider an
+            apparent use of a year or full date as the major version number
+            an error. Default True. One of the observed patterns on PyPI before
+            the introduction of `NormalizedVersion` was version numbers like this:
+                2009.01.03
+                20040603
+                2005.01
+            This guard is here to strongly encourage the package author to
+            use an alternate version, because a release deployed into PyPI
+            and, e.g. downstream Linux package managers, will forever remove
+            the possibility of using a version number like "1.0" (i.e.
+            where the major number is less than that huge major number).
+        """
+        self._parse(s, error_on_huge_major_num)
+
+    @classmethod
+    def from_parts(cls, version, prerelease=FINAL_MARKER,
+                   devpost=FINAL_MARKER):
+        return cls(cls.parts_to_str((version, prerelease, devpost)))
+
+    def _parse(self, s, error_on_huge_major_num=True):
+        """Parses a string version into parts."""
+        match = VERSION_RE.search(s)
+        if not match:
+            raise IrrationalVersionError(s)
+
+        groups = match.groupdict()
+        parts = []
+
+        # main version
+        block = self._parse_numdots(groups['version'], s, False, 2)
+        extraversion = groups.get('extraversion')
+        if extraversion not in ('', None):
+            block += self._parse_numdots(extraversion[1:], s)
+        parts.append(tuple(block))
+
+        # prerelease
+        prerel = groups.get('prerel')
+        if prerel is not None:
+            block = [prerel]
+            block += self._parse_numdots(groups.get('prerelversion'), s,
+                                         pad_zeros_length=1)
+            parts.append(tuple(block))
+        else:
+            parts.append(FINAL_MARKER)
+
+        # postdev
+        if groups.get('postdev'):
+            post = groups.get('post')
+            dev = groups.get('dev')
+            postdev = []
+            if post is not None:
+                postdev.extend([FINAL_MARKER[0], 'post', int(post)])
+                if dev is None:
+                    postdev.append(FINAL_MARKER[0])
+            if dev is not None:
+                postdev.extend(['dev', int(dev)])
+            parts.append(tuple(postdev))
+        else:
+            parts.append(FINAL_MARKER)
+        self.parts = tuple(parts)
+        if error_on_huge_major_num and self.parts[0][0] > 1980:
+            raise HugeMajorVersionNumError("huge major version number, %r, "
+                "which might cause future problems: %r" % (self.parts[0][0], s))
+
+    def _parse_numdots(self, s, full_ver_str, drop_trailing_zeros=True,
+                       pad_zeros_length=0):
+        """Parse 'N.N.N' sequences, return a list of ints.
+
+        :param s {str} 'N.N.N..." sequence to be parsed
+        :param full_ver_str {str} The full version string from which this
+            comes. Used for error strings.
+        :param drop_trailing_zeros {bool} Whether to drop trailing zeros
+            from the returned list. Default True.
+        :param pad_zeros_length {int} The length to which to pad the
+            returned list with zeros, if necessary. Default 0.
+        """
+        nums = []
+        for n in s.split("."):
+            if len(n) > 1 and n[0] == '0':
+                raise IrrationalVersionError("cannot have leading zero in "
+                    "version number segment: '%s' in %r" % (n, full_ver_str))
+            nums.append(int(n))
+        if drop_trailing_zeros:
+            while nums and nums[-1] == 0:
+                nums.pop()
+        while len(nums) < pad_zeros_length:
+            nums.append(0)
+        return nums
+
+    def __str__(self):
+        return self.parts_to_str(self.parts)
+
+    @classmethod
+    def parts_to_str(cls, parts):
+        """Transforms a version expressed in tuple into its string
+        representation."""
+        # XXX This doesn't check for invalid tuples
+        main, prerel, postdev = parts
+        s = '.'.join(str(v) for v in main)
+        if prerel is not FINAL_MARKER:
+            s += prerel[0]
+            s += '.'.join(str(v) for v in prerel[1:])
+        if postdev and postdev is not FINAL_MARKER:
+            if postdev[0] == 'f':
+                postdev = postdev[1:]
+            i = 0
+            while i < len(postdev):
+                if i % 2 == 0:
+                    s += '.'
+                s += str(postdev[i])
+                i += 1
+        return s
+
+    def __repr__(self):
+        return "%s('%s')" % (self.__class__.__name__, self)
+
+    def _cannot_compare(self, other):
+        raise TypeError("cannot compare %s and %s"
+                % (type(self).__name__, type(other).__name__))
+
+    def __eq__(self, other):
+        if not isinstance(other, NormalizedVersion):
+            self._cannot_compare(other)
+        return self.parts == other.parts
+
+    def __lt__(self, other):
+        if not isinstance(other, NormalizedVersion):
+            self._cannot_compare(other)
+        return self.parts < other.parts
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __gt__(self, other):
+        return not (self.__lt__(other) or self.__eq__(other))
+
+    def __le__(self, other):
+        return self.__eq__(other) or self.__lt__(other)
+
+    def __ge__(self, other):
+        return self.__eq__(other) or self.__gt__(other)
+
+def suggest_normalized_version(s):
+    """Suggest a normalized version close to the given version string.
+
+    If you have a version string that isn't rational (i.e. NormalizedVersion
+    doesn't like it) then you might be able to get an equivalent (or close)
+    rational version from this function.
+
+    This does a number of simple normalizations to the given string, based
+    on observation of versions currently in use on PyPI. Given a dump of
+    those version during PyCon 2009, 4287 of them:
+    - 2312 (53.93%) match NormalizedVersion without change
+    - with the automatic suggestion
+    - 3474 (81.04%) match when using this suggestion method
+
+    :param s {str} An irrational version string.
+    :returns: A rational version string, or None, if couldn't determine one.
+    """
+    try:
+        NormalizedVersion(s)
+        return s   # already rational
+    except IrrationalVersionError:
+        pass
+
+    rs = s.lower()
+
+    # part of this could use maketrans
+    for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'),
+                       ('beta', 'b'), ('rc', 'c'), ('-final', ''),
+                       ('-pre', 'c'),
+                       ('-release', ''), ('.release', ''), ('-stable', ''),
+                       ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''),
+                       ('final', '')):
+        rs = rs.replace(orig, repl)
+
+    # if something ends with dev or pre, we add a 0
+    rs = re.sub(r"pre$", r"pre0", rs)
+    rs = re.sub(r"dev$", r"dev0", rs)
+
+    # if we have something like "b-2" or "a.2" at the end of the
+    # version, that is pobably beta, alpha, etc
+    # let's remove the dash or dot
+    rs = re.sub(r"([abc|rc])[\-\.](\d+)$", r"\1\2", rs)
+
+    # 1.0-dev-r371 -> 1.0.dev371
+    # 0.1-dev-r79 -> 0.1.dev79
+    rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs)
+
+    # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1
+    rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs)
+
+    # Clean: v0.3, v1.0
+    if rs.startswith('v'):
+        rs = rs[1:]
+
+    # Clean leading '0's on numbers.
+    #TODO: unintended side-effect on, e.g., "2003.05.09"
+    # PyPI stats: 77 (~2%) better
+    rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs)
+
+    # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers
+    # zero.
+    # PyPI stats: 245 (7.56%) better
+    rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs)
+
+    # the 'dev-rNNN' tag is a dev tag
+    rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs)
+
+    # clean the - when used as a pre delimiter
+    rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs)
+
+    # a terminal "dev" or "devel" can be changed into ".dev0"
+    rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs)
+
+    # a terminal "dev" can be changed into ".dev0"
+    rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs)
+
+    # a terminal "final" or "stable" can be removed
+    rs = re.sub(r"(final|stable)$", "", rs)
+
+    # The 'r' and the '-' tags are post release tags
+    #   0.4a1.r10       ->  0.4a1.post10
+    #   0.9.33-17222    ->  0.9.3.post17222
+    #   0.9.33-r17222   ->  0.9.3.post17222
+    rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs)
+
+    # Clean 'r' instead of 'dev' usage:
+    #   0.9.33+r17222   ->  0.9.3.dev17222
+    #   1.0dev123       ->  1.0.dev123
+    #   1.0.git123      ->  1.0.dev123
+    #   1.0.bzr123      ->  1.0.dev123
+    #   0.1a0dev.123    ->  0.1a0.dev123
+    # PyPI stats:  ~150 (~4%) better
+    rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs)
+
+    # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage:
+    #   0.2.pre1        ->  0.2c1
+    #   0.2-c1         ->  0.2c1
+    #   1.0preview123   ->  1.0c123
+    # PyPI stats: ~21 (0.62%) better
+    rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs)
+
+
+    # Tcl/Tk uses "px" for their post release markers
+    rs = re.sub(r"p(\d+)$", r".post\1", rs)
+
+    try:
+        NormalizedVersion(rs)
+        return rs   # already rational
+    except IrrationalVersionError:
+        pass
+    return None
--- a/rhodecode/model/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,33 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.__init__
-    ~~~~~~~~~~~~~~~~~~~~~~~~
-
-    The application's model objects
-
-    :created_on: Nov 25, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-
-
-    :example:
-
-        .. code-block:: python
-
-           from paste.deploy import appconfig
-           from pylons import config
-           from sqlalchemy import engine_from_config
-           from rhodecode.config.environment import load_environment
-
-           conf = appconfig('config:development.ini', relative_to = './../../')
-           load_environment(conf.global_conf, conf.local_conf)
-
-           engine = engine_from_config(config, 'sqlalchemy.')
-           init_model(engine)
-           # RUN YOUR CODE HERE
-
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -40,6 +11,36 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.__init__
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The application's model objects
+
+:created_on: Nov 25, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+
+:example:
+
+    .. code-block:: python
+
+       from paste.deploy import appconfig
+       from pylons import config
+       from sqlalchemy import engine_from_config
+       from rhodecode.config.environment import load_environment
+
+       conf = appconfig('config:development.ini', relative_to = './../../')
+       load_environment(conf.global_conf, conf.local_conf)
+
+       engine = engine_from_config(config, 'sqlalchemy.')
+       init_model(engine)
+       # RUN YOUR CODE HERE
+
+"""
+
 
 import logging
 from rhodecode.model import meta
@@ -130,8 +131,9 @@
         return self._get_instance(Permission, permission,
                                   callback=Permission.get_by_key)
 
-    def get_all(self):
+    @classmethod
+    def get_all(cls):
         """
         Returns all instances of what is defined in `cls` class variable
         """
-        return self.cls.getAll()
+        return cls.cls.getAll()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/model/api_key.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.api_key
+~~~~~~~~~~~~~~~~~~~~~~~
+
+api key model for RhodeCode
+
+:created_on: Sep 8, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+import time
+import logging
+import traceback
+from sqlalchemy import or_
+
+from rhodecode.lib.utils2 import generate_api_key
+from rhodecode.model import BaseModel
+from rhodecode.model.db import UserApiKeys
+from rhodecode.model.meta import Session
+
+log = logging.getLogger(__name__)
+
+
+class ApiKeyModel(BaseModel):
+    cls = UserApiKeys
+
+    def create(self, user, description, lifetime=-1):
+        """
+        :param user: user or user_id
+        :param description: description of ApiKey
+        :param lifetime: expiration time in seconds
+        """
+        user = self._get_user(user)
+
+        new_api_key = UserApiKeys()
+        new_api_key.api_key = generate_api_key(user.username)
+        new_api_key.user_id = user.user_id
+        new_api_key.description = description
+        new_api_key.expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
+        Session().add(new_api_key)
+
+        return new_api_key
+
+    def delete(self, api_key, user=None):
+        """
+        Deletes given api_key, if user is set it also filters the object for
+        deletion by given user.
+        """
+        api_key = UserApiKeys.query().filter(UserApiKeys.api_key == api_key)
+
+        if user:
+            user = self._get_user(user)
+            api_key = api_key.filter(UserApiKeys.user_id == user.user_id)
+
+        api_key = api_key.scalar()
+        try:
+            Session().delete(api_key)
+        except Exception:
+            log.error(traceback.format_exc())
+            raise
+
+    def get_api_keys(self, user, show_expired=True):
+        user = self._get_user(user)
+        user_api_keys = UserApiKeys.query()\
+            .filter(UserApiKeys.user_id == user.user_id)
+        if not show_expired:
+            user_api_keys = user_api_keys\
+                .filter(or_(UserApiKeys.expires == -1,
+                            UserApiKeys.expires >= time.time()))
+        return user_api_keys
--- a/rhodecode/model/changeset_status.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/changeset_status.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,13 +1,14 @@
 # -*- coding: utf-8 -*-
 """
-    rhodecode.model.changeset_status
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+rhodecode.model.changeset_status
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+Changeset status conttroller
 
-    :created_on: Apr 30, 2012
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
+:created_on: Apr 30, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
 """
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
--- a/rhodecode/model/comment.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/comment.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.comment
-    ~~~~~~~~~~~~~~~~~~~~~~~
-
-    comments model for RhodeCode
-
-    :created_on: Nov 11, 2011
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.comment
+~~~~~~~~~~~~~~~~~~~~~~~
+
+comments model for RhodeCode
+
+:created_on: Nov 11, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import logging
 import traceback
--- a/rhodecode/model/db.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/db.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.db
-    ~~~~~~~~~~~~~~~~~~
-
-    Database Models for RhodeCode
-
-    :created_on: Apr 08, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.db
+~~~~~~~~~~~~~~~~~~
+
+Database Models for RhodeCode
+
+:created_on: Apr 08, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import os
 import time
@@ -30,6 +30,7 @@
 import traceback
 import hashlib
 import collections
+import functools
 
 from sqlalchemy import *
 from sqlalchemy.ext.hybrid import hybrid_property
@@ -47,7 +48,8 @@
 from rhodecode.lib.vcs.backends.base import EmptyChangeset
 
 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
+    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \
+    get_clone_url
 from rhodecode.lib.compat import json
 from rhodecode.lib.caching_query import FromCache
 
@@ -146,7 +148,10 @@
     def __repr__(self):
         if hasattr(self, '__unicode__'):
             # python repr needs to return str
-            return safe_str(self.__unicode__())
+            try:
+                return safe_str(self.__unicode__())
+            except UnicodeDecodeError:
+                pass
         return '<DB:%s>' % (self.__class__.__name__)
 
 
@@ -155,15 +160,27 @@
     __table_args__ = (
         UniqueConstraint('app_settings_name'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
+
+    SETTINGS_TYPES = {
+        'str': safe_str,
+        'int': safe_int,
+        'unicode': safe_unicode,
+        'bool': str2bool,
+        'list': functools.partial(aslist, sep=',')
+    }
+    DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
+
     app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, k='', v=''):
-        self.app_settings_name = k
-        self.app_settings_value = v
+    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None)
+    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+
+    def __init__(self, key='', val='', type='unicode'):
+        self.app_settings_name = key
+        self.app_settings_value = val
+        self.app_settings_type = type
 
     @validates('_app_settings_value')
     def validate_settings_value(self, key, val):
@@ -173,13 +190,9 @@
     @hybrid_property
     def app_settings_value(self):
         v = self._app_settings_value
-        if self.app_settings_name in ["ldap_active",
-                                      "default_repo_enable_statistics",
-                                      "default_repo_enable_locking",
-                                      "default_repo_private",
-                                      "default_repo_enable_downloads"]:
-            v = str2bool(v)
-        return v
+        _type = self.app_settings_type
+        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
+        return converter(v)
 
     @app_settings_value.setter
     def app_settings_value(self, val):
@@ -190,10 +203,21 @@
         """
         self._app_settings_value = safe_unicode(val)
 
+    @hybrid_property
+    def app_settings_type(self):
+        return self._app_settings_type
+
+    @app_settings_type.setter
+    def app_settings_type(self, val):
+        if val not in self.SETTINGS_TYPES:
+            raise Exception('type must be one of %s got %s'
+                            % (self.SETTINGS_TYPES.keys(), val))
+        self._app_settings_type = val
+
     def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
+        return u"<%s('%s:%s[%s]')>" % (
             self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value
+            self.app_settings_name, self.app_settings_value, self.app_settings_type
         )
 
     @classmethod
@@ -202,10 +226,36 @@
             .filter(cls.app_settings_name == key).scalar()
 
     @classmethod
-    def get_by_name_or_create(cls, key):
+    def get_by_name_or_create(cls, key, val='', type='unicode'):
         res = cls.get_by_name(key)
         if not res:
-            res = cls(key)
+            res = cls(key, val, type)
+        return res
+
+    @classmethod
+    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
+        """
+        Creates or updates RhodeCode setting. If updates is triggered it will only
+        update parameters that are explicityl set Optional instance will be skipped
+
+        :param key:
+        :param val:
+        :param type:
+        :return:
+        """
+        res = cls.get_by_name(key)
+        if not res:
+            val = Optional.extract(val)
+            type = Optional.extract(type)
+            res = cls(key, val, type)
+        else:
+            res.app_settings_name = key
+            if not isinstance(val, Optional):
+                # update if set
+                res.app_settings_value = val
+            if not isinstance(type, Optional):
+                # update if set
+                res.app_settings_type = type
         return res
 
     @classmethod
@@ -226,9 +276,14 @@
         return settings
 
     @classmethod
-    def get_ldap_settings(cls, cache=False):
+    def get_auth_plugins(cls, cache=False):
+        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
+        return auth_plugins
+
+    @classmethod
+    def get_auth_settings(cls, cache=False):
         ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('ldap_')).all()
+                .filter(cls.app_settings_name.startswith('auth_')).all()
         fd = {}
         for row in ret:
             fd.update({row.app_settings_name: row.app_settings_value})
@@ -248,13 +303,30 @@
 
         return fd
 
+    @classmethod
+    def get_server_info(cls):
+        import pkg_resources
+        import platform
+        import rhodecode
+        from rhodecode.lib.utils import check_git_version
+        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
+        info = {
+            'modules': sorted(mods, key=lambda k: k[0].lower()),
+            'py_version': platform.python_version(),
+            'platform': safe_unicode(platform.platform()),
+            'rhodecode_version': rhodecode.__version__,
+            'git_version': safe_unicode(check_git_version()),
+            'git_path': rhodecode.CONFIG.get('git_path')
+        }
+        return info
+
 
 class RhodeCodeUi(Base, BaseModel):
     __tablename__ = 'rhodecode_ui'
     __table_args__ = (
         UniqueConstraint('ui_key'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
 
     HOOK_UPDATE = 'changegroup.update'
@@ -265,9 +337,9 @@
     HOOK_PRE_PULL = 'preoutgoing.pre_pull'
 
     ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
     ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
 
     # def __init__(self, section='', key='', value=''):
@@ -311,8 +383,8 @@
         Session().add(new_ui)
 
     def __repr__(self):
-        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
-                                   self.ui_value)
+        return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
+                                    self.ui_key, self.ui_value)
 
 
 class User(Base, BaseModel):
@@ -322,22 +394,26 @@
         Index('u_username_idx', 'username'),
         Index('u_email_idx', 'email'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     DEFAULT_USER = 'default'
+    DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
 
     user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
     active = Column("active", Boolean(), nullable=True, unique=None, default=True)
     admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
-    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
     last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
     inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    _user_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data
 
     user_log = relationship('UserLog')
     user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
@@ -358,6 +434,9 @@
     user_comments = relationship('ChangesetComment', cascade='all')
     #extra emails for this user
     user_emails = relationship('UserEmailMap', cascade='all')
+    #extra api keys
+    user_api_keys = relationship('UserApiKeys', cascade='all')
+
 
     @hybrid_property
     def email(self):
@@ -378,6 +457,11 @@
         return [self.email] + [x.email for x in other]
 
     @property
+    def api_keys(self):
+        other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
+        return [self.api_key] + [x.api_key for x in other]
+
+    @property
     def ip_addresses(self):
         ret = UserIpMap.query().filter(UserIpMap.user == self).all()
         return [x.ip_addr for x in ret]
@@ -416,9 +500,26 @@
         return AuthUser(user_id=self.user_id, api_key=self.api_key,
                         username=self.username)
 
+    @hybrid_property
+    def user_data(self):
+        if not self._user_data:
+            return {}
+
+        try:
+            return json.loads(self._user_data)
+        except TypeError:
+            return {}
+
+    @user_data.setter
+    def user_data(self, val):
+        try:
+            self._user_data = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
     def __unicode__(self):
         return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                     self.user_id, self.username)
+                                      self.user_id, self.username)
 
     @classmethod
     def get_by_username(cls, username, case_insensitive=False, cache=False):
@@ -436,13 +537,24 @@
         return q.scalar()
 
     @classmethod
-    def get_by_api_key(cls, api_key, cache=False):
+    def get_by_api_key(cls, api_key, cache=False, fallback=True):
         q = cls.query().filter(cls.api_key == api_key)
 
         if cache:
             q = q.options(FromCache("sql_cache_short",
                                     "get_api_key_%s" % api_key))
-        return q.scalar()
+        res = q.scalar()
+
+        if fallback and not res:
+            #fallback to additional keys
+            _res = UserApiKeys.query()\
+                .filter(UserApiKeys.api_key == api_key)\
+                .filter(or_(UserApiKeys.expires == -1,
+                            UserApiKeys.expires >= time.time()))\
+                .first()
+            if _res:
+                res = _res.user
+        return res
 
     @classmethod
     def get_by_email(cls, email, case_insensitive=False, cache=False):
@@ -524,9 +636,11 @@
             email=user.email,
             emails=user.emails,
             api_key=user.api_key,
+            api_keys=user.api_keys,
             active=user.active,
             admin=user.admin,
-            ldap_dn=user.ldap_dn,
+            extern_type=user.extern_type,
+            extern_name=user.extern_name,
             last_login=user.last_login,
             ip_addresses=user.ip_addresses
         )
@@ -543,19 +657,46 @@
         return data
 
 
+class UserApiKeys(Base, BaseModel):
+    __tablename__ = 'user_api_keys'
+    __table_args__ = (
+        Index('uak_api_key_idx', 'api_key'),
+        Index('uak_api_key_expires_idx', 'api_key', 'expires'),
+        UniqueConstraint('api_key'),
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+    )
+    __mapper_args__ = {}
+
+    user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True)
+    description = Column('description', UnicodeText(1024))
+    expires = Column('expires', Float(53), nullable=False)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+    user = relationship('User', lazy='joined')
+
+    @property
+    def expired(self):
+        if self.expires == -1:
+            return False
+        return time.time() > self.expires
+
+
 class UserEmailMap(Base, BaseModel):
     __tablename__ = 'user_email_map'
     __table_args__ = (
         Index('uem_email_idx', 'email'),
         UniqueConstraint('email'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     __mapper_args__ = {}
 
     email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
     user = relationship('User', lazy='joined')
 
     @validates('_email')
@@ -580,13 +721,13 @@
     __table_args__ = (
         UniqueConstraint('user_id', 'ip_addr'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     __mapper_args__ = {}
 
     ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
     active = Column("active", Boolean(), nullable=True, unique=None, default=True)
     user = relationship('User', lazy='joined')
 
@@ -602,20 +743,23 @@
           ip_range=self._get_ip_range(self.ip_addr)
         )
 
+    def __unicode__(self):
+        return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
+                                            self.user_id, self.ip_addr)
 
 class UserLog(Base, BaseModel):
     __tablename__ = 'user_logs'
     __table_args__ = (
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
     user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
     repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
-    repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None)
     action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
 
     def __unicode__(self):
@@ -635,14 +779,17 @@
     __tablename__ = 'users_groups'
     __table_args__ = (
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
 
     users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
     users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
     inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    _group_data = Column("group_data", LargeBinary(), nullable=True)  # JSON data
 
     members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
     users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
@@ -653,6 +800,23 @@
 
     user = relationship('User')
 
+    @hybrid_property
+    def group_data(self):
+        if not self._group_data:
+            return {}
+
+        try:
+            return json.loads(self._group_data)
+        except TypeError:
+            return {}
+
+    @group_data.setter
+    def group_data(self, val):
+        try:
+            self._group_data = json.dumps(val)
+        except Exception:
+            log.error(traceback.format_exc())
+
     def __unicode__(self):
         return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
                                       self.users_group_id,
@@ -674,21 +838,29 @@
         return q.scalar()
 
     @classmethod
-    def get(cls, users_group_id, cache=False):
-        users_group = cls.query()
+    def get(cls, user_group_id, cache=False):
+        user_group = cls.query()
         if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % users_group_id))
-        return users_group.get(users_group_id)
-
-    def get_api_data(self):
-        users_group = self
+            user_group = user_group.options(FromCache("sql_cache_short",
+                                    "get_users_group_%s" % user_group_id))
+        return user_group.get(user_group_id)
+
+    def get_api_data(self, with_members=True):
+        user_group = self
 
         data = dict(
-            users_group_id=users_group.users_group_id,
-            group_name=users_group.users_group_name,
-            active=users_group.users_group_active,
+            users_group_id=user_group.users_group_id,
+            group_name=user_group.users_group_name,
+            group_description=user_group.user_group_description,
+            active=user_group.users_group_active,
+            owner=user_group.user.username,
         )
+        if with_members:
+            members = []
+            for user in user_group.members:
+                user = user.user
+                members.append(user.get_api_data())
+            data['members'] = members
 
         return data
 
@@ -697,7 +869,7 @@
     __tablename__ = 'users_groups_members'
     __table_args__ = (
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
 
     users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -717,16 +889,16 @@
     __table_args__ = (
         UniqueConstraint('repository_id', 'field_key'),  # no-multi field
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
     PREFIX = 'ex_'  # prefix used in form to not conflict with already existing fields
 
     repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-    field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
-    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
+    field_key = Column("field_key", String(250, convert_unicode=False))
+    field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False)
+    field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False)
+    field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False)
     field_type = Column("field_type", String(256), nullable=False, unique=None)
     created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
@@ -756,23 +928,31 @@
         UniqueConstraint('repo_name'),
         Index('r_repo_name_idx', 'repo_name'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
+    DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
+    DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
+
+    STATE_CREATED = 'repo_state_created'
+    STATE_PENDING = 'repo_state_pending'
+    STATE_ERROR = 'repo_state_error'
 
     repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    repo_state = Column("repo_state", String(255), nullable=True)
+
+    clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
     private = Column("private", Boolean(), nullable=True, unique=None, default=None)
     enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
     enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
-    description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
     created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
     updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+    _landing_revision = Column("landing_revision", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
     enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    _locked = Column("locked", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
     _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
 
     fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
@@ -804,7 +984,24 @@
 
     def __unicode__(self):
         return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   self.repo_name)
+                                   safe_unicode(self.repo_name))
+
+    @hybrid_property
+    def landing_rev(self):
+        # always should return [rev_type, rev]
+        if self._landing_revision:
+            _rev_info = self._landing_revision.split(':')
+            if len(_rev_info) < 2:
+                _rev_info.insert(0, 'rev')
+            return [_rev_info[0], _rev_info[1]]
+        return [None, None]
+
+    @landing_rev.setter
+    def landing_rev(self, val):
+        if ':' not in val:
+            raise ValueError('value must be delimited with `:` and consist '
+                             'of <rev_type>:<rev>, got %s instead' % val)
+        self._landing_revision = val
 
     @hybrid_property
     def locked(self):
@@ -1038,25 +1235,48 @@
     def last_db_change(self):
         return self.updated_on
 
+    @property
+    def clone_uri_hidden(self):
+        clone_uri = self.clone_uri
+        if clone_uri:
+            import urlobject
+            url_obj = urlobject.URLObject(self.clone_uri)
+            if url_obj.password:
+                clone_uri = url_obj.with_password('*****')
+        return clone_uri
+
     def clone_url(self, **override):
         from pylons import url
-        from urlparse import urlparse
-        import urllib
-        parsed_url = urlparse(url('home', qualified=True))
-        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
-        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
-        args = {
-           'user': '',
-           'pass': '',
-           'scheme': parsed_url.scheme,
-           'netloc': parsed_url.netloc,
-           'prefix': decoded_path,
-           'path': self.repo_name
-        }
-
-        args.update(override)
-        return default_clone_uri % args
-
+        qualified_home_url = url('home', qualified=True)
+
+        uri_tmpl = None
+        if 'with_id' in override:
+            uri_tmpl = self.DEFAULT_CLONE_URI_ID
+            del override['with_id']
+
+        if 'uri_tmpl' in override:
+            uri_tmpl = override['uri_tmpl']
+            del override['uri_tmpl']
+
+        # we didn't override our tmpl from **overrides
+        if not uri_tmpl:
+            uri_tmpl = self.DEFAULT_CLONE_URI
+            try:
+                from pylons import tmpl_context as c
+                uri_tmpl = c.clone_uri_tmpl
+            except Exception:
+                # in any case if we call this outside of request context,
+                # ie, not having tmpl_context set up
+                pass
+
+        return get_clone_url(uri_tmpl=uri_tmpl,
+                             qualifed_home_url=qualified_home_url,
+                             repo_name=self.repo_name,
+                             repo_id=self.repo_id, **override)
+
+    def set_state(self, state):
+        self.repo_state = state
+        Session().add(self)
     #==========================================================================
     # SCM PROPERTIES
     #==========================================================================
@@ -1068,7 +1288,10 @@
         """
         Returns landing changeset, or if that doesn't exist returns the tip
         """
-        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
+        _rev_type, _rev = self.landing_rev
+        cs = self.get_changeset(_rev)
+        if isinstance(cs, EmptyChangeset):
+            return self.get_changeset()
         return cs
 
     def update_changeset_cache(self, cs_cache=None):
@@ -1233,6 +1456,8 @@
 
         return repo
 
+    def __json__(self):
+        return dict(landing_rev = self.landing_rev)
 
 class RepoGroup(Base, BaseModel):
     __tablename__ = 'groups'
@@ -1240,16 +1465,19 @@
         UniqueConstraint('group_name', 'group_parent_id'),
         CheckConstraint('group_id != group_parent_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
     __mapper_args__ = {'order_by': 'group_name'}
 
+    SEP = ' &raquo; '
+
     group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
     group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
     enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
     users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
@@ -1265,21 +1493,23 @@
                                       self.group_name)
 
     @classmethod
+    def _generate_choice(cls, repo_group):
+        from webhelpers.html import literal as _literal
+        _name = lambda k: _literal(cls.SEP.join(k))
+        return repo_group.group_id, _name(repo_group.full_path_splitted)
+
+    @classmethod
     def groups_choices(cls, groups=None, show_empty_group=True):
-        from webhelpers.html import literal as _literal
         if not groups:
             groups = cls.query().all()
 
         repo_groups = []
         if show_empty_group:
             repo_groups = [('-1', u'-- %s --' % _('top level'))]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
+
+        repo_groups.extend([cls._generate_choice(x) for x in groups])
+
+        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0])
         return repo_groups
 
     @classmethod
@@ -1304,7 +1534,7 @@
 
     @property
     def parents(self):
-        parents_recursion_limit = 5
+        parents_recursion_limit = 10
         groups = []
         if self.parent_group is None:
             return groups
@@ -1319,8 +1549,8 @@
                 break
             if cnt == parents_recursion_limit:
                 # this will prevent accidental infinit loops
-                log.error('group nested more than %s' %
-                          parents_recursion_limit)
+                log.error(('more than %s parents found for group %s, stopping '
+                           'recursive parent fetching' % (parents_recursion_limit, self)))
                 break
 
             groups.insert(0, gr)
@@ -1399,13 +1629,29 @@
                        self.parent_group else [])
         return RepoGroup.url_sep().join(path_prefix + [group_name])
 
+    def get_api_data(self):
+        """
+        Common function for generating api data
+
+        """
+        group = self
+        data = dict(
+            group_id=group.group_id,
+            group_name=group.group_name,
+            group_description=group.group_description,
+            parent_group=group.parent_group.group_name if group.parent_group else None,
+            repositories=[x.repo_name for x in group.repositories],
+            owner=group.user.username
+        )
+        return data
+
 
 class Permission(Base, BaseModel):
     __tablename__ = 'permissions'
     __table_args__ = (
         Index('p_perm_name_idx', 'permission_name'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
     PERMS = [
         ('hg.admin', _('RhodeCode Administrator')),
@@ -1433,6 +1679,8 @@
 
         ('hg.create.none', _('Repository creation disabled')),
         ('hg.create.repository', _('Repository creation enabled')),
+        ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
+        ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
 
         ('hg.fork.none', _('Repository forking disabled')),
         ('hg.fork.repository', _('Repository forking enabled')),
@@ -1452,6 +1700,7 @@
         'group.read',
         'usergroup.read',
         'hg.create.repository',
+        'hg.create.write_on_repogroup.true',
         'hg.fork.repository',
         'hg.register.manual_activate',
         'hg.extern_activate.auto',
@@ -1488,8 +1737,8 @@
     }
 
     permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
     def __unicode__(self):
         return u"<%s('%s:%s')>" % (
@@ -1533,7 +1782,7 @@
     __table_args__ = (
         UniqueConstraint('user_id', 'repository_id', 'permission_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
@@ -1562,7 +1811,7 @@
     __table_args__ = (
         UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
@@ -1591,7 +1840,7 @@
     __table_args__ = (
         UniqueConstraint('user_id', 'permission_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
@@ -1609,7 +1858,7 @@
     __table_args__ = (
         UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
@@ -1639,7 +1888,7 @@
         UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
         CheckConstraint('target_user_group_id != user_group_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
@@ -1668,7 +1917,7 @@
     __table_args__ = (
         UniqueConstraint('users_group_id', 'permission_id',),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
@@ -1683,7 +1932,7 @@
     __table_args__ = (
         UniqueConstraint('user_id', 'group_id', 'permission_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
 
     group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -1695,13 +1944,22 @@
     group = relationship('RepoGroup')
     permission = relationship('Permission')
 
+    @classmethod
+    def create(cls, user, repository_group, permission):
+        n = cls()
+        n.user = user
+        n.group = repository_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
 
 class UserGroupRepoGroupToPerm(Base, BaseModel):
     __tablename__ = 'users_group_repo_group_to_perm'
     __table_args__ = (
         UniqueConstraint('users_group_id', 'group_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
 
     users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -1713,13 +1971,22 @@
     permission = relationship('Permission')
     group = relationship('RepoGroup')
 
+    @classmethod
+    def create(cls, user_group, repository_group, permission):
+        n = cls()
+        n.users_group = user_group
+        n.group = repository_group
+        n.permission = permission
+        Session().add(n)
+        return n
+
 
 class Statistics(Base, BaseModel):
     __tablename__ = 'statistics'
     __table_args__ = (
          UniqueConstraint('repository_id'),
          {'extend_existing': True, 'mysql_engine': 'InnoDB',
-          'mysql_charset': 'utf8'}
+          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
@@ -1737,7 +2004,7 @@
         UniqueConstraint('user_id', 'follows_repository_id'),
         UniqueConstraint('user_id', 'follows_user_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
 
     user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -1762,14 +2029,14 @@
         UniqueConstraint('cache_key'),
         Index('key_idx', 'cache_key'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
     # cache_id, not used
     cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     # cache_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
     # cache_args is a repo_name
-    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
     # instance sets cache_active True when it is caching,
     # other instances set cache_active to False to indicate that this cache is invalid
     cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
@@ -1825,18 +2092,22 @@
         return "%s%s" % (prefix, key)
 
     @classmethod
-    def set_invalidate(cls, repo_name):
+    def set_invalidate(cls, repo_name, delete=False):
         """
         Mark all caches of a repo as invalid in the database.
         """
         inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-
+        log.debug('for repo %s got %s invalidation objects'
+                  % (safe_str(repo_name), inv_objs))
         try:
             for inv_obj in inv_objs:
                 log.debug('marking %s key for invalidation based on repo_name=%s'
                           % (inv_obj, safe_str(repo_name)))
-                inv_obj.cache_active = False
-                Session().add(inv_obj)
+                if delete:
+                    Session().delete(inv_obj)
+                else:
+                    inv_obj.cache_active = False
+                    Session().add(inv_obj)
             Session().commit()
         except Exception:
             log.error(traceback.format_exc())
@@ -1884,7 +2155,7 @@
     __table_args__ = (
         Index('cc_revision_idx', 'revision'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
     comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
     repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
@@ -1928,7 +2199,7 @@
         Index('cs_version_idx', 'version'),
         UniqueConstraint('repo_id', 'revision', 'version'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
     STATUS_APPROVED = 'approved'
@@ -1976,7 +2247,7 @@
     __tablename__ = 'pull_requests'
     __table_args__ = (
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
 
     # values for .status
@@ -2031,7 +2302,7 @@
 
     def __json__(self):
         return dict(
-          revisions=self.revisions
+            revisions=self.revisions
         )
 
 
@@ -2039,7 +2310,7 @@
     __tablename__ = 'pull_request_reviewers'
     __table_args__ = (
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
 
     def __init__(self, user=None, pull_request=None):
@@ -2059,7 +2330,7 @@
     __table_args__ = (
         Index('notification_type_idx', 'type'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
 
     TYPE_CHANGESET_COMMENT = u'cs_comment'
@@ -2116,7 +2387,7 @@
     __table_args__ = (
         UniqueConstraint('user_id', 'notification_id'),
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
     user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
     notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
@@ -2142,6 +2413,7 @@
     )
     GIST_PUBLIC = u'public'
     GIST_PRIVATE = u'private'
+    DEFAULT_FILENAME = u'gistfile1.txt'
 
     gist_id = Column('gist_id', Integer(), primary_key=True)
     gist_access_id = Column('gist_access_id', Unicode(250))
@@ -2154,6 +2426,9 @@
 
     owner = relationship('User')
 
+    def __repr__(self):
+        return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
+
     @classmethod
     def get_or_404(cls, id_):
         res = cls.query().filter(cls.gist_access_id == id_).scalar()
@@ -2222,7 +2497,7 @@
     __tablename__ = 'db_migrate_version'
     __table_args__ = (
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
+         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
     repository_id = Column('repository_id', String(250), primary_key=True)
     repository_path = Column('repository_path', Text)
--- a/rhodecode/model/forms.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/forms.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,4 +1,18 @@
-""" this is forms validation classes
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+this is forms validation classes
 http://formencode.org/module-formencode.validators.html
 for list off all availible validators
 
@@ -26,8 +40,8 @@
 
 from pylons.i18n.translation import _
 
+from rhodecode import BACKENDS
 from rhodecode.model import validators as v
-from rhodecode import BACKENDS
 
 log = logging.getLogger(__name__)
 
@@ -58,6 +72,20 @@
     chained_validators = [v.ValidAuth()]
 
 
+def PasswordChangeForm(username):
+    class _PasswordChangeForm(formencode.Schema):
+        allow_extra_fields = True
+        filter_extra_fields = True
+
+        current_password = v.ValidOldPassword(username)(not_empty=True)
+        new_password = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
+        new_password_confirmation = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
+
+        chained_validators = [v.ValidPasswordsMatch('new_password',
+                                                    'new_password_confirmation')]
+    return _PasswordChangeForm
+
+
 def UserForm(edit=False, old_data={}):
     class _UserForm(formencode.Schema):
         allow_extra_fields = True
@@ -88,9 +116,9 @@
         firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
         lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
         email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
-
+        extern_name = v.UnicodeString(strip=True)
+        extern_type = v.UnicodeString(strip=True)
         chained_validators = [v.ValidPasswordsMatch()]
-
     return _UserForm
 
 
@@ -103,6 +131,8 @@
             v.UnicodeString(strip=True, min=1, not_empty=True),
             v.ValidUserGroup(edit, old_data)
         )
+        user_group_description = v.UnicodeString(strip=True, min=1,
+                                                 not_empty=False)
 
         users_group_active = v.StringBoolean(if_missing=False)
 
@@ -115,16 +145,19 @@
     return _UserGroupForm
 
 
-def ReposGroupForm(edit=False, old_data={}, available_groups=[],
+def RepoGroupForm(edit=False, old_data={}, available_groups=[],
                    can_create_in_root=False):
-    class _ReposGroupForm(formencode.Schema):
+    class _RepoGroupForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = False
 
         group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
-                               v.SlugifyName())
+                         v.SlugifyName(),
+                         v.ValidRegex(msg=_('Name must not contain only digits'))(r'(?!^\d+$)^.+$'))
         group_description = v.UnicodeString(strip=True, min=1,
-                                                not_empty=False)
+                                            not_empty=False)
+        group_copy_permissions = v.StringBoolean(if_missing=False)
+
         if edit:
             #FIXME: do a special check that we cannot move a group to one of
             #it's children
@@ -134,9 +167,9 @@
                                       testValueList=True,
                                       if_missing=None, not_empty=True))
         enable_locking = v.StringBoolean(if_missing=False)
-        chained_validators = [v.ValidReposGroup(edit, old_data)]
+        chained_validators = [v.ValidRepoGroup(edit, old_data)]
 
-    return _ReposGroupForm
+    return _RepoGroupForm
 
 
 def RegisterForm(edit=False, old_data={}):
@@ -187,6 +220,7 @@
         repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
         repo_private = v.StringBoolean(if_missing=False)
         repo_landing_rev = v.OneOf(landing_revs, hideList=True)
+        repo_copy_permissions = v.StringBoolean(if_missing=False)
         clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
 
         repo_enable_statistics = v.StringBoolean(if_missing=False)
@@ -196,6 +230,7 @@
         if edit:
             #this is repo owner
             user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
+            clone_uri_change = v.UnicodeString(not_empty=False, if_missing=v.Missing)
 
         chained_validators = [v.ValidCloneUri(),
                               v.ValidRepoName(edit, old_data)]
@@ -210,11 +245,11 @@
     return _RepoPermsForm
 
 
-def RepoGroupPermsForm():
+def RepoGroupPermsForm(valid_recursive_choices):
     class _RepoGroupPermsForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = False
-        recursive = v.StringBoolean(if_missing=False)
+        recursive = v.OneOf(valid_recursive_choices)
         chained_validators = [v.ValidPerms(type_='repo_group')]
     return _RepoGroupPermsForm
 
@@ -268,9 +303,11 @@
     class _ApplicationSettingsForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = False
-        rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
+        rhodecode_title = v.UnicodeString(strip=True, not_empty=False)
         rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
         rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
+        rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
+        rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
 
     return _ApplicationSettingsForm
 
@@ -286,7 +323,11 @@
         rhodecode_repository_fields = v.StringBoolean(if_missing=False)
         rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
         rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
+        rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
         rhodecode_show_version = v.StringBoolean(if_missing=False)
+        rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
+        rhodecode_gravatar_url = v.UnicodeString(min=3)
+        rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
 
     return _ApplicationVisualisationForm
 
@@ -314,8 +355,9 @@
 
 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
                            user_group_perms_choices, create_choices,
-                           repo_group_create_choices, user_group_create_choices,
-                           fork_choices, register_choices, extern_activate_choices):
+                           create_on_write_choices, repo_group_create_choices,
+                           user_group_create_choices, fork_choices,
+                           register_choices, extern_activate_choices):
     class _DefaultPermissionsForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = True
@@ -328,6 +370,7 @@
         default_user_group_perm = v.OneOf(user_group_perms_choices)
 
         default_repo_create = v.OneOf(create_choices)
+        create_on_write = v.OneOf(create_on_write_choices)
         default_user_group_create = v.OneOf(user_group_create_choices)
         #default_repo_group_create = v.OneOf(repo_group_create_choices) #not impl. yet
         default_fork = v.OneOf(fork_choices)
@@ -365,6 +408,42 @@
     return _DefaultsForm
 
 
+def AuthSettingsForm(current_active_modules):
+    class _AuthSettingsForm(formencode.Schema):
+        allow_extra_fields = True
+        filter_extra_fields = True
+        auth_plugins = All(v.ValidAuthPlugins(),
+                           v.UniqueListFromString()(not_empty=True))
+
+        def __init__(self, *args, **kwargs):
+            # The auth plugins tell us what form validators they use
+            if current_active_modules:
+                import rhodecode.lib.auth_modules
+                from rhodecode.lib.auth_modules import LazyFormencode
+                for module in current_active_modules:
+                    plugin = rhodecode.lib.auth_modules.loadplugin(module)
+                    plugin_name = plugin.name
+                    for sv in plugin.plugin_settings():
+                        newk = "auth_%s_%s" % (plugin_name, sv["name"])
+                        # can be a LazyFormencode object from plugin settings
+                        validator = sv["validator"]
+                        if isinstance(validator, LazyFormencode):
+                            validator = validator()
+                        #init all lazy validators from formencode.All
+                        if isinstance(validator, All):
+                            init_validators = []
+                            for validator in validator.validators:
+                                if isinstance(validator, LazyFormencode):
+                                    validator = validator()
+                                init_validators.append(validator)
+                            validator.validators = init_validators
+
+                        self.add_field(newk, validator)
+            formencode.Schema.__init__(self, *args, **kwargs)
+
+    return _AuthSettingsForm
+
+
 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
                      tls_kind_choices):
     class _LdapSettingsForm(formencode.Schema):
@@ -412,8 +491,8 @@
         other_repo = v.UnicodeString(strip=True, required=True)
         other_ref = v.UnicodeString(strip=True, required=True)
         revisions = All(#v.NotReviewedRevisions(repo_id)(),
-                        v.UniqueList(not_empty=True))
-        review_members = v.UniqueList(not_empty=True)
+                        v.UniqueList()(not_empty=True))
+        review_members = v.UniqueList()(not_empty=True)
 
         pullrequest_title = v.UnicodeString(strip=True, required=True)
         pullrequest_desc = v.UnicodeString(strip=True, required=False)
--- a/rhodecode/model/gist.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/gist.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.gist
-    ~~~~~~~~~~~~~~~~~~~~
-
-    gist model for RhodeCode
-
-    :created_on: May 9, 2013
-    :author: marcink
-    :copyright: (C) 2011-2013 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.gist
+~~~~~~~~~~~~~~~~~~~~
+
+gist model for RhodeCode
+
+:created_on: May 9, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 from __future__ import with_statement
 import os
 import time
@@ -46,6 +47,7 @@
 
 
 class GistModel(BaseModel):
+    cls = Gist
 
     def _get_gist(self, gist):
         """
@@ -53,8 +55,7 @@
 
         :param gist: GistID, gist_access_id, or Gist instance
         """
-        return self._get_instance(Gist, gist,
-                                  callback=Gist.get_by_access_id)
+        return self._get_instance(Gist, gist, callback=Gist.get_by_access_id)
 
     def __delete_gist(self, gist):
         """
@@ -64,23 +65,39 @@
         """
         root_path = RepoModel().repos_path
         rm_path = os.path.join(root_path, GIST_STORE_LOC, gist.gist_access_id)
-        log.info("Removing %s" % (rm_path))
+        log.info("Removing %s" % (rm_path,))
         shutil.rmtree(rm_path)
 
+    def _store_metadata(self, repo, gist_id, gist_access_id, user_id, gist_type,
+                        gist_expires):
+        """
+        store metadata inside the gist, this can be later used for imports
+        or gist identification
+        """
+        metadata = {
+            'metadata_version': '1',
+            'gist_db_id': gist_id,
+            'gist_access_id': gist_access_id,
+            'gist_owner_id': user_id,
+            'gist_type': gist_type,
+            'gist_expires': gist_expires,
+            'gist_updated': time.time(),
+        }
+        with open(os.path.join(repo.path, '.hg', GIST_METADATA_FILE), 'wb') as f:
+            f.write(json.dumps(metadata))
+
     def get_gist(self, gist):
         return self._get_gist(gist)
 
-    def get_gist_files(self, gist_access_id):
+    def get_gist_files(self, gist_access_id, revision=None):
         """
         Get files for given gist
 
         :param gist_access_id:
         """
         repo = Gist.get_by_access_id(gist_access_id)
-        cs = repo.scm_instance.get_changeset()
-        return (
-         cs, [n for n in cs.get_node('/')]
-        )
+        cs = repo.scm_instance.get_changeset(revision)
+        return cs, [n for n in cs.get_node('/')]
 
     def create(self, description, owner, gist_mapping,
                gist_type=Gist.GIST_PUBLIC, lifetime=-1):
@@ -92,6 +109,7 @@
         :param gist_type: type of gist private/public
         :param lifetime: in minutes, -1 == forever
         """
+        owner = self._get_user(owner)
         gist_id = safe_unicode(unique_id(20))
         lifetime = safe_int(lifetime, -1)
         gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
@@ -115,8 +133,8 @@
 
         gist_repo_path = os.path.join(GIST_STORE_LOC, gist_id)
         log.debug('Creating new %s GIST repo in %s' % (gist_type, gist_repo_path))
-        repo = RepoModel()._create_repo(repo_name=gist_repo_path, alias='hg',
-                                        parent=None)
+        repo = RepoModel()._create_filesystem_repo(
+            repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC)
 
         processed_mapping = {}
         for filename in gist_mapping:
@@ -127,7 +145,8 @@
             #TODO: expand support for setting explicit lexers
 #             if lexer is None:
 #                 try:
-#                     lexer = pygments.lexers.guess_lexer_for_filename(filename,content)
+#                     guess_lexer = pygments.lexers.guess_lexer_for_filename
+#                     lexer = guess_lexer(filename,content)
 #                 except pygments.util.ClassNotFound:
 #                     lexer = 'text'
             processed_mapping[filename] = {'content': content}
@@ -148,29 +167,74 @@
             nodes=processed_mapping,
             trigger_push_hook=False
         )
-        # store metadata inside the gist, this can be later used for imports
-        # or gist identification
-        metadata = {
-            'gist_db_id': gist.gist_id,
-            'gist_access_id': gist.gist_access_id,
-            'gist_owner_id': owner.user_id,
-            'gist_type': gist.gist_type,
-            'gist_exipres': gist.gist_expires
-        }
-        with open(os.path.join(repo.path, '.hg', GIST_METADATA_FILE), 'wb') as f:
-            f.write(json.dumps(metadata))
+
+        self._store_metadata(repo, gist.gist_id, gist.gist_access_id,
+                             owner.user_id, gist.gist_type, gist.gist_expires)
         return gist
 
     def delete(self, gist, fs_remove=True):
         gist = self._get_gist(gist)
-
         try:
             self.sa.delete(gist)
             if fs_remove:
                 self.__delete_gist(gist)
             else:
                 log.debug('skipping removal from filesystem')
-
         except Exception:
             log.error(traceback.format_exc())
             raise
+
+    def update(self, gist, description, owner, gist_mapping, gist_type,
+               lifetime):
+        gist = self._get_gist(gist)
+        gist_repo = gist.scm_instance
+
+        lifetime = safe_int(lifetime, -1)
+        if lifetime == 0:  # preserve old value
+            gist_expires = gist.gist_expires
+        else:
+            gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
+
+        #calculate operation type based on given data
+        gist_mapping_op = {}
+        for k, v in gist_mapping.items():
+            # add, mod, del
+            if not v['org_filename'] and v['filename']:
+                op = 'add'
+            elif v['org_filename'] and not v['filename']:
+                op = 'del'
+            else:
+                op = 'mod'
+
+            v['op'] = op
+            gist_mapping_op[k] = v
+
+        gist.gist_description = description
+        gist.gist_expires = gist_expires
+        gist.owner = owner
+        gist.gist_type = gist_type
+        self.sa.add(gist)
+        self.sa.flush()
+
+        message = 'updated file'
+        message += 's: ' if len(gist_mapping) > 1 else ': '
+        message += ', '.join([x for x in gist_mapping])
+
+        #fake RhodeCode Repository object
+        fake_repo = AttributeDict(dict(
+            repo_name=gist_repo.path,
+            scm_instance_no_cache=lambda: gist_repo,
+        ))
+
+        self._store_metadata(gist_repo, gist.gist_id, gist.gist_access_id,
+                             owner.user_id, gist.gist_type, gist.gist_expires)
+
+        ScmModel().update_nodes(
+            user=owner.user_id,
+            repo=fake_repo,
+            message=message,
+            nodes=gist_mapping_op,
+            trigger_push_hook=False
+        )
+
+        return gist
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/model/license.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.license
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Model for licenses
+
+
+:created_on: Aug 1, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+import uuid
+import base64
+import time
+import logging
+import traceback
+
+from Crypto import Random
+from Crypto.Cipher import AES
+
+import rhodecode
+from rhodecode.lib.compat import json
+from rhodecode.model import BaseModel
+from rhodecode.model.db import RhodeCodeSetting
+
+log = logging.getLogger(__name__)
+
+BLOCK_SIZE = 32
+
+
+def pad(s):
+    return (s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) *
+            chr(BLOCK_SIZE - len(s) % BLOCK_SIZE))
+
+
+def unpad(s):
+    return s[0:-ord(s[-1])]
+
+
+class LicenseModel(BaseModel):
+
+    cls = RhodeCodeSetting
+
+    def __init__(self, sa=None, key=None):
+        super(LicenseModel, self).__init__(sa=sa)
+        if not key:
+            key = rhodecode.CONFIG.get('license_token')
+
+        if not key:
+            raise TypeError('Missing encryption key: %s' % key)
+        if not isinstance(key, basestring):
+            raise TypeError('Encryption key is bad type, got %s '
+                            'expected basestring' % type(key))
+        #strip dashes
+        self.key = key.replace('-', '')
+
+    @classmethod
+    def generate_license_token(cls):
+        tok_len = 4
+        hex_token = uuid.uuid1().hex
+        num_pools = [hex_token[start:start+tok_len]
+                     for start in range(0, len(hex_token), tok_len)]
+        return u'-'.join(num_pools[:4])
+
+    @classmethod
+    def get_default_license(cls):
+
+        key = 'abra-cada-bra1-rce3'
+        enc_license_key = ('''
+            0HBgVSMJgapshyHBZGiSj/pqSLkO8BmkbSnFDw8j+e854FkSJuVyUhIyUSFgs5mJ+P
+            KGknkw/LOf0pKLRZ7EZiFW+lkrv99UaS395FCp4J+ymdg7YdIdYtc0FZ8TcZjXrV3y
+            ORR3Klsjf7PUV6XVYSU6CAMx5SyK78n3JwYLclCnog1lD4kwHEFJH+OD5jFG3OT9eN
+            Kd0c1rVIJbbkPfppVIa4twr52bXWjueOpamdFI7DOJtlse9XyY1axgGNVQDtgRlWi5
+            YmtP8OfT+Rq9SaCMQzPq3R42/YWHHrG1fhEMO7DAuruyLASoXROAo+hZmDoWYiqOP2
+            vtJk/vc6WTB0Ro5zBWznFvkvTPzpFNf+A4FlQzQrVLjVOdIKncyJLx7QFHYFWT9ewD
+            Abr3XpaN5brqe97hDslN/8uKZabUydYI4dKDYkuc5+WAOfPGuqM/CwIPjZQ6K8ivJK
+            yX3CHBThwQuJtQY85GHPte0fT0bHoQyLIBwwYa76/pdm4eTaUrMZbj2HipDGTRO6BU
+            DaDIdw5YiczQ+Jec5phmqwbJ5Z/uXzdV6dhFLDTaiQ+PSkRLg9F1/cPZZxYOo8Jatn
+            6pSQvmzi4ALTsaPIGGyu5aazPRB0Wz7g2tyPfUcAP5rzS0aWIdoszsAXizBiJdKgr4
+            X2SlOqJ3MYfen4rvLbIQwV2IiRJdtv1QoFGAyyGfDtGzYruZbpfQcouhwRJbaESTwB
+            0WXMa73Q2jw59GiTB5C4U=''')
+        return json.loads(LicenseModel(key=key).decrypt(enc_license_key))
+
+    @classmethod
+    def get_license_info(cls, license_token, enc_license_key, safe=True,
+                         fill_defaults=False):
+        license_info = {}
+        if fill_defaults:
+            license_info = cls.get_default_license()
+        try:
+            if license_token and enc_license_key:
+                license_info = json.loads(
+                    LicenseModel(key=license_token).decrypt(enc_license_key))
+        except Exception, e:
+            log.error(traceback.format_exc())
+            if not safe:
+                raise
+        return license_info
+
+    @classmethod
+    def get_license_key(cls):
+        defaults = RhodeCodeSetting.get_app_settings()
+        return defaults.get('rhodecode_license_key')
+
+    def encrypt(self, text, key=None):
+        if not isinstance(text, basestring):
+            raise TypeError('Encrypt can only work on unicode or '
+                            'string, got %s' % type(text))
+        if not key:
+            key = self.key
+        padded_text = pad(text)
+        IV = Random.new().read(AES.block_size)
+        cipher = AES.new(key, AES.MODE_CBC, IV)
+        return base64.b64encode(IV + cipher.encrypt(padded_text))
+
+    def decrypt(self, enc_text, key=None):
+        if not isinstance(enc_text, (unicode, str)):
+            raise TypeError('Encrypt can only work on unicode or '
+                            'string, got %s' % type(enc_text))
+        if not key:
+            key = self.key
+        enc = base64.b64decode(enc_text)
+        iv = enc[:16]  # iv is stored
+        cipher = AES.new(key, AES.MODE_CBC, iv)
+        return unpad(cipher.decrypt(enc[16:]))
+
+    def generate_signature(self, license_key, sig_key):
+        copy = license_key.copy()
+        del copy['signature']
+        return self.encrypt(json.dumps(copy), key=sig_key)
+
+    def verify(self, enc_text, sig_key):
+        if not isinstance(enc_text, basestring):
+            raise TypeError('Encrypt can only work on unicode or '
+                            'string, got %s' % type(enc_text))
+
+        decrypted = json.loads(self.decrypt(enc_text))
+        try:
+            signature = json.loads(self.decrypt(decrypted['signature'], sig_key))
+        except Exception:
+            signature = '-- decryption error --'
+
+        del decrypted['signature']
+        #TODO: write better diff display
+        if decrypted != signature:
+            raise TypeError('Signature mismatch got %s[%s] vs %s[%s]'
+                    % (decrypted, type(decrypted), signature, type(signature)))
+        return signature
--- a/rhodecode/model/meta.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/meta.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,4 +1,19 @@
-"""SQLAlchemy Metadata and Session object"""
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+SQLAlchemy Metadata and Session object
+"""
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import scoped_session, sessionmaker
 from beaker import cache
--- a/rhodecode/model/notification.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/notification.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.notification
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Model for notifications
-
-
-    :created_on: Nov 20, 2011
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,6 +11,19 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.notification
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Model for notifications
+
+
+:created_on: Nov 20, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import os
 import logging
@@ -255,17 +256,18 @@
     TYPE_DEFAULT = 'default'
 
     def __init__(self):
+        super(EmailNotificationModel, self).__init__()
         self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
         self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
+        self.email_types = {
+            self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
+            self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
+            self.TYPE_REGISTRATION: 'email_templates/registration.html',
+            self.TYPE_DEFAULT: 'email_templates/default.html',
+            self.TYPE_PULL_REQUEST: 'email_templates/pull_request.html',
+            self.TYPE_PULL_REQUEST_COMMENT: 'email_templates/pull_request_comment.html',
+        }
 
-        self.email_types = {
-         self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
-         self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
-         self.TYPE_REGISTRATION: 'email_templates/registration.html',
-         self.TYPE_DEFAULT: 'email_templates/default.html',
-         self.TYPE_PULL_REQUEST: 'email_templates/pull_request.html',
-         self.TYPE_PULL_REQUEST_COMMENT: 'email_templates/pull_request_comment.html',
-        }
 
     def get_email_tmpl(self, type_, **kwargs):
         """
--- a/rhodecode/model/permission.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/permission.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.permission
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    permissions model for RhodeCode
-
-    :created_on: Aug 20, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.permission
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+permissions model for RhodeCode
+
+:created_on: Aug 20, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import logging
 import traceback
@@ -54,9 +55,10 @@
                 new_perm.permission_longname = p[0]  #translation err with p[1]
                 self.sa.add(new_perm)
 
-    def create_default_permissions(self, user):
+    def create_default_permissions(self, user, force=False):
         """
-        Creates only missing default permissions for user
+        Creates only missing default permissions for user, if force is set it
+        resets the default permissions for that user
 
         :param user:
         """
@@ -77,6 +79,11 @@
         log.debug('GOT ALREADY DEFINED:%s' % perms)
         DEFAULT_PERMS = Permission.DEFAULT_USER_PERMISSIONS
 
+        if force:
+            for perm in perms:
+                self.sa.delete(perm)
+            self.sa.commit()
+            defined_perms_groups = []
         # for every default permission that needs to be created, we check if
         # it's group is already defined, if it's not we create default perm
         for perm_name in DEFAULT_PERMS:
@@ -92,7 +99,7 @@
 
         try:
             # stage 1 set anonymous access
-            if perm_user.username == 'default':
+            if perm_user.username == User.DEFAULT_USER:
                 perm_user.active = str2bool(form_result['anonymous'])
                 self.sa.add(perm_user)
 
@@ -112,12 +119,15 @@
             for p in u2p:
                 self.sa.delete(p)
             #create fresh set of permissions
-            for def_perm_key in ['default_repo_perm', 'default_group_perm',
+            for def_perm_key in ['default_repo_perm',
+                                 'default_group_perm',
                                  'default_user_group_perm',
                                  'default_repo_create',
+                                 'create_on_write', # special case for create repos on write access to group
                                  #'default_repo_group_create', #not implemented yet
                                  'default_user_group_create',
-                                 'default_fork', 'default_register',
+                                 'default_fork',
+                                 'default_register',
                                  'default_extern_activate']:
                 p = _make_new(perm_user, form_result[def_perm_key])
                 self.sa.add(p)
--- a/rhodecode/model/pull_request.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/pull_request.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.pull_request
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    pull request model for RhodeCode
-
-    :created_on: Jun 6, 2012
-    :author: marcink
-    :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.pull_request
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+pull request model for RhodeCode
+
+:created_on: Jun 6, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import logging
 import datetime
--- a/rhodecode/model/repo.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/repo.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.repo
-    ~~~~~~~~~~~~~~~~~~~~
-
-    Repository model for rhodecode
-
-    :created_on: Jun 5, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,24 +11,40 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.repo
+~~~~~~~~~~~~~~~~~~~~
+
+Repository model for rhodecode
+
+:created_on: Jun 5, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+"""
+
 from __future__ import with_statement
 import os
 import shutil
 import logging
 import traceback
 from datetime import datetime
+from rhodecode.lib.utils import make_ui
 
 from rhodecode.lib.vcs.backends import get_backend
 from rhodecode.lib.compat import json
-from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode,\
+from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode, \
     remove_prefix, obfuscate_url_pw, get_current_rhodecode_user
 from rhodecode.lib.caching_query import FromCache
 from rhodecode.lib.hooks import log_create_repository, log_delete_repository
 
 from rhodecode.model import BaseModel
-from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
-    Statistics, UserGroup, UserGroupRepoToPerm, RhodeCodeUi, RepoGroup,\
+from rhodecode.model.db import Repository, UserRepoToPerm, UserGroupRepoToPerm, \
+    UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission, \
+    Statistics, UserGroup, RhodeCodeUi, RepoGroup, \
     RhodeCodeSetting, RepositoryField
+
 from rhodecode.lib import helpers as h
 from rhodecode.lib.auth import HasRepoPermissionAny, HasUserGroupPermissionAny
 from rhodecode.lib.exceptions import AttachedForksError
@@ -57,8 +62,8 @@
         return self._get_instance(UserGroup, users_group,
                                   callback=UserGroup.get_by_group_name)
 
-    def _get_repo_group(self, repos_group):
-        return self._get_instance(RepoGroup, repos_group,
+    def _get_repo_group(self, repo_group):
+        return self._get_instance(RepoGroup, repo_group,
                                   callback=RepoGroup.get_by_group_name)
 
     def _create_default_perms(self, repository, private):
@@ -90,7 +95,7 @@
         return q.ui_value
 
     def get(self, repo_id, cache=False):
-        repo = self.sa.query(Repository)\
+        repo = self.sa.query(Repository) \
             .filter(Repository.repo_id == repo_id)
 
         if cache:
@@ -102,7 +107,7 @@
         return self._get_repo(repository)
 
     def get_by_repo_name(self, repo_name, cache=False):
-        repo = self.sa.query(Repository)\
+        repo = self.sa.query(Repository) \
             .filter(Repository.repo_name == repo_name)
 
         if cache:
@@ -129,26 +134,26 @@
         users = self.sa.query(User).filter(User.active == True).all()
         return json.dumps([
             {
-             'id': u.user_id,
-             'fname': u.name,
-             'lname': u.lastname,
-             'nname': u.username,
-             'gravatar_lnk': h.gravatar_url(u.email, 14)
+                'id': u.user_id,
+                'fname': u.name,
+                'lname': u.lastname,
+                'nname': u.username,
+                'gravatar_lnk': h.gravatar_url(u.email, 14)
             } for u in users]
         )
 
-    def get_users_groups_js(self):
-        users_groups = self.sa.query(UserGroup)\
+    def get_user_groups_js(self):
+        user_groups = self.sa.query(UserGroup) \
             .filter(UserGroup.users_group_active == True).all()
-        users_groups = UserGroupList(users_groups, perm_set=['usergroup.read',
-                                                             'usergroup.write',
-                                                             'usergroup.admin'])
+        user_groups = UserGroupList(user_groups, perm_set=['usergroup.read',
+                                                           'usergroup.write',
+                                                           'usergroup.admin'])
         return json.dumps([
             {
-             'id': gr.users_group_id,
-             'grname': gr.users_group_name,
-             'grmembers': len(gr.members),
-            } for gr in users_groups]
+                'id': gr.users_group_id,
+                'grname': gr.users_group_name,
+                'grmembers': len(gr.members),
+            } for gr in user_groups]
         )
 
     @classmethod
@@ -179,8 +184,8 @@
         def quick_menu(repo_name):
             return _render('quick_menu', repo_name)
 
-        def repo_lnk(name, rtype, private, fork_of):
-            return _render('repo_name', name, rtype, private, fork_of,
+        def repo_lnk(name, rtype, rstate, private, fork_of):
+            return _render('repo_name', name, rtype, rstate, private, fork_of,
                            short_name=not admin, admin=False)
 
         def last_change(last_change):
@@ -203,6 +208,9 @@
             else:
                 return h.urlify_text(h.truncate(desc, 60))
 
+        def state(repo_state):
+            return _render("repo_state", repo_state)
+
         def repo_actions(repo_name):
             return _render('repo_actions', repo_name, super_user_actions)
 
@@ -214,7 +222,8 @@
             if perm_check:
                 # check permission at this level
                 if not HasRepoPermissionAny(
-                    'repository.read', 'repository.write', 'repository.admin'
+                        'repository.read', 'repository.write',
+                        'repository.admin'
                 )(repo.repo_name, 'get_repos_as_dict check'):
                     continue
             cs_cache = repo.changeset_cache
@@ -222,12 +231,13 @@
                 "menu": quick_menu(repo.repo_name),
                 "raw_name": repo.repo_name.lower(),
                 "name": repo_lnk(repo.repo_name, repo.repo_type,
-                                 repo.private, repo.fork),
+                                 repo.repo_state, repo.private, repo.fork),
                 "last_change": last_change(repo.last_db_change),
                 "last_changeset": last_rev(repo.repo_name, cs_cache),
-                "raw_tip": cs_cache.get('revision'),
+                "last_rev_raw": cs_cache.get('revision'),
                 "desc": desc(repo.description),
                 "owner": h.person(repo.user.username),
+                "state": state(repo.repo_state),
                 "rss": rss_lnk(repo.repo_name),
                 "atom": atom_lnk(repo.repo_name),
 
@@ -268,14 +278,19 @@
                                          'group_id', None)
 
         for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
-                  (1, 'repo_description'), (1, 'repo_enable_locking'),
-                  (1, 'repo_landing_rev'), (0, 'clone_uri'),
-                  (1, 'repo_private'), (1, 'repo_enable_statistics')]:
+                         (1, 'repo_description'), (1, 'repo_enable_locking'),
+                         (1, 'repo_landing_rev'), (0, 'clone_uri'),
+                         (1, 'repo_private'), (1, 'repo_enable_statistics')]:
             attr = k
             if strip:
                 attr = remove_prefix(k, 'repo_')
 
-            defaults[k] = defaults[attr]
+            val = defaults[attr]
+            if k == 'repo_landing_rev':
+                val = ':'.join(defaults[attr])
+            defaults[k] = val
+            if k == 'clone_uri':
+                defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
 
         # fill owner
         if repo_info.user:
@@ -288,33 +303,43 @@
         # fill repository users
         for p in repo_info.repo_to_perm:
             defaults.update({'u_perm_%s' % p.user.username:
-                             p.permission.permission_name})
+                                 p.permission.permission_name})
 
         # fill repository groups
         for p in repo_info.users_group_to_perm:
             defaults.update({'g_perm_%s' % p.users_group.users_group_name:
-                             p.permission.permission_name})
+                                 p.permission.permission_name})
 
         return defaults
 
-    def update(self, org_repo_name, **kwargs):
+    def update(self, repo, **kwargs):
         try:
-            cur_repo = self.get_by_repo_name(org_repo_name, cache=False)
-
+            cur_repo = self._get_repo(repo)
+            org_repo_name = cur_repo.repo_name
             if 'user' in kwargs:
                 cur_repo.user = User.get_by_username(kwargs['user'])
 
             if 'repo_group' in kwargs:
                 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
-
+            log.debug('Updating repo %s with params:%s' % (cur_repo, kwargs))
             for strip, k in [(1, 'repo_enable_downloads'),
-                      (1, 'repo_description'), (1, 'repo_enable_locking'),
-                      (1, 'repo_landing_rev'), (0, 'clone_uri'),
-                      (1, 'repo_private'), (1, 'repo_enable_statistics')]:
+                             (1, 'repo_description'),
+                             (1, 'repo_enable_locking'),
+                             (1, 'repo_landing_rev'),
+                             (1, 'repo_private'),
+                             (1, 'repo_enable_statistics'),
+                             (0, 'clone_uri'),]:
                 if k in kwargs:
                     val = kwargs[k]
                     if strip:
                         k = remove_prefix(k, 'repo_')
+                    if k == 'clone_uri':
+                        from rhodecode.model.validators import Missing
+                        _change = kwargs.get('clone_uri_change')
+                        if _change == Missing:
+                            # we don't change the value, so use original one
+                            val = cur_repo.clone_uri
+
                     setattr(cur_repo, k, val)
 
             new_name = cur_repo.get_new_name(kwargs['repo_name'])
@@ -326,8 +351,9 @@
                 RepoModel().grant_user_permission(
                     repo=cur_repo, user='default', perm=EMPTY_PERM
                 )
-            #handle extra fields
-            for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
+                #handle extra fields
+            for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
+                                kwargs):
                 k = RepositoryField.un_prefix_key(field)
                 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
                 if ex_field:
@@ -337,29 +363,32 @@
 
             if org_repo_name != new_name:
                 # rename repository
-                self.__rename_repo(old=org_repo_name, new=new_name)
+                self._rename_filesystem_repo(old=org_repo_name, new=new_name)
 
             return cur_repo
         except Exception:
             log.error(traceback.format_exc())
             raise
 
-    def create_repo(self, repo_name, repo_type, description, owner,
-                    private=False, clone_uri=None, repos_group=None,
-                    landing_rev='tip', just_db=False, fork_of=None,
-                    copy_fork_permissions=False, enable_statistics=False,
-                    enable_locking=False, enable_downloads=False):
+    def _create_repo(self, repo_name, repo_type, description, owner,
+                     private=False, clone_uri=None, repo_group=None,
+                     landing_rev='rev:tip', fork_of=None,
+                     copy_fork_permissions=False, enable_statistics=False,
+                     enable_locking=False, enable_downloads=False,
+                     copy_group_permissions=False, state=Repository.STATE_PENDING):
         """
-        Create repository
+        Create repository inside database with PENDING state, this should be
+        only executed by create() repo. With exception of importing existing repos
 
         """
         from rhodecode.model.scm import ScmModel
 
         owner = self._get_user(owner)
         fork_of = self._get_repo(fork_of)
-        repos_group = self._get_repo_group(repos_group)
+        repo_group = self._get_repo_group(repo_group)
         try:
-
+            repo_name = safe_unicode(repo_name)
+            description = safe_unicode(description)
             # 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
@@ -367,11 +396,12 @@
             repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
 
             new_repo = Repository()
+            new_repo.repo_state = state
             new_repo.enable_statistics = False
             new_repo.repo_name = repo_name_full
             new_repo.repo_type = repo_type
             new_repo.user = owner
-            new_repo.group = repos_group
+            new_repo.group = repo_group
             new_repo.description = description or repo_name
             new_repo.private = private
             new_repo.clone_uri = clone_uri
@@ -381,8 +411,8 @@
             new_repo.enable_locking = enable_locking
             new_repo.enable_downloads = enable_downloads
 
-            if repos_group:
-                new_repo.enable_locking = repos_group.enable_locking
+            if repo_group:
+                new_repo.enable_locking = repo_group.enable_locking
 
             if fork_of:
                 parent_repo = fork_of
@@ -390,74 +420,62 @@
 
             self.sa.add(new_repo)
 
-            if fork_of:
-                if copy_fork_permissions:
-                    repo = fork_of
-                    user_perms = UserRepoToPerm.query()\
-                        .filter(UserRepoToPerm.repository == repo).all()
-                    group_perms = UserGroupRepoToPerm.query()\
-                        .filter(UserGroupRepoToPerm.repository == repo).all()
+            if fork_of and copy_fork_permissions:
+                repo = fork_of
+                user_perms = UserRepoToPerm.query() \
+                    .filter(UserRepoToPerm.repository == repo).all()
+                group_perms = UserGroupRepoToPerm.query() \
+                    .filter(UserGroupRepoToPerm.repository == repo).all()
+
+                for perm in user_perms:
+                    UserRepoToPerm.create(perm.user, new_repo, perm.permission)
+
+                for perm in group_perms:
+                    UserGroupRepoToPerm.create(perm.users_group, new_repo,
+                                               perm.permission)
+
+            elif repo_group and copy_group_permissions:
 
-                    for perm in user_perms:
-                        UserRepoToPerm.create(perm.user, new_repo,
-                                              perm.permission)
+                user_perms = UserRepoGroupToPerm.query() \
+                    .filter(UserRepoGroupToPerm.group == repo_group).all()
+
+                group_perms = UserGroupRepoGroupToPerm.query() \
+                    .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
 
-                    for perm in group_perms:
-                        UserGroupRepoToPerm.create(perm.users_group, new_repo,
-                                                    perm.permission)
-                else:
-                    perm_obj = self._create_default_perms(new_repo, private)
-                    self.sa.add(perm_obj)
+                for perm in user_perms:
+                    perm_name = perm.permission.permission_name.replace('group.', 'repository.')
+                    perm_obj = Permission.get_by_key(perm_name)
+                    UserRepoToPerm.create(perm.user, new_repo, perm_obj)
+
+                for perm in group_perms:
+                    perm_name = perm.permission.permission_name.replace('group.', 'repository.')
+                    perm_obj = Permission.get_by_key(perm_name)
+                    UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
+
             else:
                 perm_obj = self._create_default_perms(new_repo, private)
                 self.sa.add(perm_obj)
 
-            if not just_db:
-                self.__create_repo(repo_name, repo_type,
-                                   repos_group,
-                                   clone_uri)
-                log_create_repository(new_repo.get_dict(),
-                                      created_by=owner.username)
-
             # now automatically start following this repository as owner
             ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
                                                     owner.user_id)
+            # we need to flush here, in order to check if database won't
+            # throw any exceptions, create filesystem dirs at the very end
+            self.sa.flush()
             return new_repo
         except Exception:
             log.error(traceback.format_exc())
             raise
 
-    def create(self, form_data, cur_user, just_db=False, fork=None):
+    def create(self, form_data, cur_user):
         """
-        Backward compatibility function, just a wrapper on top of create_repo
+        Create repository using celery tasks
 
         :param form_data:
         :param cur_user:
-        :param just_db:
-        :param fork:
         """
-        owner = cur_user
-        repo_name = form_data['repo_name_full']
-        repo_type = form_data['repo_type']
-        description = form_data['repo_description']
-        private = form_data['repo_private']
-        clone_uri = form_data.get('clone_uri')
-        repos_group = form_data['repo_group']
-        landing_rev = form_data['repo_landing_rev']
-        copy_fork_permissions = form_data.get('copy_permissions')
-        fork_of = form_data.get('fork_parent_id')
-
-        ## repo creation defaults, private and repo_type are filled in form
-        defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
-        enable_statistics = defs.get('repo_enable_statistics')
-        enable_locking = defs.get('repo_enable_locking')
-        enable_downloads = defs.get('repo_enable_downloads')
-
-        return self.create_repo(
-            repo_name, repo_type, description, owner, private, clone_uri,
-            repos_group, landing_rev, just_db, fork_of, copy_fork_permissions,
-            enable_statistics, enable_locking, enable_downloads
-        )
+        from rhodecode.lib.celerylib import tasks, run_task
+        return run_task(tasks.create_repo, form_data, cur_user)
 
     def _update_permissions(self, repo, perms_new=None, perms_updates=None,
                             check_perms=True):
@@ -475,12 +493,14 @@
                 )
             else:
                 #check if we have permissions to alter this usergroup
-                req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
-                if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
-                    self.grant_users_group_permission(
+                req_perms = (
+                    'usergroup.read', 'usergroup.write', 'usergroup.admin')
+                if not check_perms or HasUserGroupPermissionAny(*req_perms)(
+                        member):
+                    self.grant_user_group_permission(
                         repo=repo, group_name=member, perm=perm
                     )
-        # set new permissions
+            # set new permissions
         for member, perm, member_type in perms_new:
             if member_type == 'user':
                 self.grant_user_permission(
@@ -488,9 +508,11 @@
                 )
             else:
                 #check if we have permissions to alter this usergroup
-                req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
-                if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
-                    self.grant_users_group_permission(
+                req_perms = (
+                    'usergroup.read', 'usergroup.write', 'usergroup.admin')
+                if not check_perms or HasUserGroupPermissionAny(*req_perms)(
+                        member):
+                    self.grant_user_group_permission(
                         repo=repo, group_name=member, perm=perm
                     )
 
@@ -502,7 +524,7 @@
         :param cur_user:
         """
         from rhodecode.lib.celerylib import tasks, run_task
-        run_task(tasks.create_repo_fork, form_data, cur_user)
+        return run_task(tasks.create_repo_fork, form_data, cur_user)
 
     def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
         """
@@ -533,7 +555,7 @@
             try:
                 self.sa.delete(repo)
                 if fs_remove:
-                    self.__delete_repo(repo)
+                    self._delete_filesystem_repo(repo)
                 else:
                     log.debug('skipping removal from filesystem')
                 log_delete_repository(old_repo_dict,
@@ -556,9 +578,9 @@
         permission = self._get_perm(perm)
 
         # check if we have that permission already
-        obj = self.sa.query(UserRepoToPerm)\
-            .filter(UserRepoToPerm.user == user)\
-            .filter(UserRepoToPerm.repository == repo)\
+        obj = self.sa.query(UserRepoToPerm) \
+            .filter(UserRepoToPerm.user == user) \
+            .filter(UserRepoToPerm.repository == repo) \
             .scalar()
         if obj is None:
             # create new !
@@ -568,6 +590,7 @@
         obj.permission = permission
         self.sa.add(obj)
         log.debug('Granted perm %s to %s on %s' % (perm, user, repo))
+        return obj
 
     def revoke_user_permission(self, repo, user):
         """
@@ -580,15 +603,15 @@
         user = self._get_user(user)
         repo = self._get_repo(repo)
 
-        obj = self.sa.query(UserRepoToPerm)\
-            .filter(UserRepoToPerm.repository == repo)\
-            .filter(UserRepoToPerm.user == user)\
+        obj = self.sa.query(UserRepoToPerm) \
+            .filter(UserRepoToPerm.repository == repo) \
+            .filter(UserRepoToPerm.user == user) \
             .scalar()
         if obj:
             self.sa.delete(obj)
             log.debug('Revoked perm on %s on %s' % (repo, user))
 
-    def grant_users_group_permission(self, repo, group_name, perm):
+    def grant_user_group_permission(self, repo, group_name, perm):
         """
         Grant permission for user group on given repository, or update
         existing one if found
@@ -603,9 +626,9 @@
         permission = self._get_perm(perm)
 
         # check if we have that permission already
-        obj = self.sa.query(UserGroupRepoToPerm)\
-            .filter(UserGroupRepoToPerm.users_group == group_name)\
-            .filter(UserGroupRepoToPerm.repository == repo)\
+        obj = self.sa.query(UserGroupRepoToPerm) \
+            .filter(UserGroupRepoToPerm.users_group == group_name) \
+            .filter(UserGroupRepoToPerm.repository == repo) \
             .scalar()
 
         if obj is None:
@@ -617,8 +640,9 @@
         obj.permission = permission
         self.sa.add(obj)
         log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo))
+        return obj
 
-    def revoke_users_group_permission(self, repo, group_name):
+    def revoke_user_group_permission(self, repo, group_name):
         """
         Revoke permission for user group on given repository
 
@@ -629,9 +653,9 @@
         repo = self._get_repo(repo)
         group_name = self._get_user_group(group_name)
 
-        obj = self.sa.query(UserGroupRepoToPerm)\
-            .filter(UserGroupRepoToPerm.repository == repo)\
-            .filter(UserGroupRepoToPerm.users_group == group_name)\
+        obj = self.sa.query(UserGroupRepoToPerm) \
+            .filter(UserGroupRepoToPerm.repository == repo) \
+            .filter(UserGroupRepoToPerm.users_group == group_name) \
             .scalar()
         if obj:
             self.sa.delete(obj)
@@ -645,21 +669,16 @@
         """
         repo = self._get_repo(repo_name)
         try:
-            obj = self.sa.query(Statistics)\
-                    .filter(Statistics.repository == repo).scalar()
+            obj = self.sa.query(Statistics) \
+                .filter(Statistics.repository == repo).scalar()
             if obj:
                 self.sa.delete(obj)
         except Exception:
             log.error(traceback.format_exc())
             raise
 
-    def _create_repo(self, repo_name, alias, parent, clone_uri=False,
-                     repo_store_location=None):
-        return self.__create_repo(repo_name, alias, parent, clone_uri,
-                                  repo_store_location)
-
-    def __create_repo(self, repo_name, alias, parent, clone_uri=False,
-                      repo_store_location=None):
+    def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
+                                clone_uri=None, repo_store_location=None):
         """
         makes repository on filesystem. It's group aware means it'll create
         a repository within a group, and alter the paths accordingly of
@@ -667,22 +686,26 @@
 
         :param repo_name:
         :param alias:
-        :param parent_id:
+        :param parent:
         :param clone_uri:
-        :param repo_path:
+        :param repo_store_location:
         """
-        from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
+        from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
         from rhodecode.model.scm import ScmModel
 
-        if parent:
-            new_parent_path = os.sep.join(parent.full_path_splitted)
+        if '/' in repo_name:
+            raise ValueError('repo_name must not contain groups got `%s`' % repo_name)
+
+        if isinstance(repo_group, RepoGroup):
+            new_parent_path = os.sep.join(repo_group.full_path_splitted)
         else:
-            new_parent_path = ''
+            new_parent_path = repo_group or ''
+
         if repo_store_location:
             _paths = [repo_store_location]
         else:
             _paths = [self.repos_path, new_parent_path, repo_name]
-        # we need to make it str for mercurial
+            # we need to make it str for mercurial
         repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
 
         # check if this path is not a repository
@@ -690,26 +713,35 @@
             raise Exception('This path %s is a valid repository' % repo_path)
 
         # check if this path is a group
-        if is_valid_repos_group(repo_path, self.repos_path):
+        if is_valid_repo_group(repo_path, self.repos_path):
             raise Exception('This path %s is a valid group' % repo_path)
 
-        log.info('creating repo %s in %s @ %s' % (
-                     repo_name, safe_unicode(repo_path),
-                     obfuscate_url_pw(clone_uri)
-                )
-        )
-        backend = get_backend(alias)
-        if alias == 'hg':
-            repo = backend(repo_path, create=True, src_url=clone_uri)
-        elif alias == 'git':
+        log.info('creating repo %s in %s from url: `%s`' % (
+            repo_name, safe_unicode(repo_path),
+            obfuscate_url_pw(clone_uri)))
+
+        backend = get_backend(repo_type)
+
+        if repo_type == 'hg':
+            baseui = make_ui('db', clear_session=False)
+            # patch and reset hooks section of UI config to not run any
+            # hooks on creating remote repo
+            for k, v in baseui.configitems('hooks'):
+                baseui.setconfig('hooks', k, None)
+
+            repo = backend(repo_path, create=True, src_url=clone_uri, baseui=baseui)
+        elif repo_type == 'git':
             repo = backend(repo_path, create=True, src_url=clone_uri, bare=True)
             # add rhodecode hook into this repo
             ScmModel().install_git_hook(repo=repo)
         else:
-            raise Exception('Undefined alias %s' % alias)
+            raise Exception('Not supported repo_type %s expected hg/git' % repo_type)
+
+        log.debug('Created repo %s with %s backend'
+                  % (safe_unicode(repo_name), safe_unicode(repo_type)))
         return repo
 
-    def __rename_repo(self, old, new):
+    def _rename_filesystem_repo(self, old, new):
         """
         renames repository on filesystem
 
@@ -726,7 +758,7 @@
             )
         shutil.move(old_path, new_path)
 
-    def __delete_repo(self, repo):
+    def _delete_filesystem_repo(self, repo):
         """
         removes repo from filesystem, the removal is acctually made by
         added rm__ prefix into dir, and rename internat .hg/.git dirs so this
@@ -736,17 +768,19 @@
         :param repo: repo object
         """
         rm_path = os.path.join(self.repos_path, repo.repo_name)
-        log.info("Removing %s" % (rm_path))
+        log.info("Removing repository %s" % (rm_path,))
         # disable hg/git internal that it doesn't get detected as repo
         alias = repo.repo_type
 
         bare = getattr(repo.scm_instance, 'bare', False)
 
+        # skip this for bare git repos
         if not bare:
-            # skip this for bare git repos
-            shutil.move(os.path.join(rm_path, '.%s' % alias),
-                        os.path.join(rm_path, 'rm__.%s' % alias))
-        # disable repo
+            # disable VCS repo
+            vcs_path = os.path.join(rm_path, '.%s' % alias)
+            if os.path.exists(vcs_path):
+                shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
+
         _now = datetime.now()
         _ms = str(_now.microsecond).rjust(6, '0')
         _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
@@ -754,4 +788,6 @@
         if repo.group:
             args = repo.group.full_path_splitted + [_d]
             _d = os.path.join(*args)
-        shutil.move(rm_path, os.path.join(self.repos_path, _d))
+
+        if os.path.isdir(rm_path):
+            shutil.move(rm_path, os.path.join(self.repos_path, _d))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/model/repo_group.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,540 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.user_group
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+repo group model for RhodeCode
+
+:created_on: Jan 25, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
+
+import os
+import logging
+import traceback
+import shutil
+import datetime
+
+from rhodecode.lib.utils2 import LazyProperty
+
+from rhodecode.model import BaseModel
+from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
+    User, Permission, UserGroupRepoGroupToPerm, UserGroup, Repository
+
+log = logging.getLogger(__name__)
+
+
+class RepoGroupModel(BaseModel):
+
+    cls = RepoGroup
+
+    def _get_user_group(self, users_group):
+        return self._get_instance(UserGroup, users_group,
+                                  callback=UserGroup.get_by_group_name)
+
+    def _get_repo_group(self, repo_group):
+        return self._get_instance(RepoGroup, repo_group,
+                                  callback=RepoGroup.get_by_group_name)
+
+    @LazyProperty
+    def repos_path(self):
+        """
+        Gets the repositories root path from database
+        """
+
+        q = RhodeCodeUi.get_by_key('/')
+        return q.ui_value
+
+    def _create_default_perms(self, new_group):
+        # create default permission
+        default_perm = 'group.read'
+        def_user = User.get_default_user()
+        for p in def_user.user_perms:
+            if p.permission.permission_name.startswith('group.'):
+                default_perm = p.permission.permission_name
+                break
+
+        repo_group_to_perm = UserRepoGroupToPerm()
+        repo_group_to_perm.permission = Permission.get_by_key(default_perm)
+
+        repo_group_to_perm.group = new_group
+        repo_group_to_perm.user_id = def_user.user_id
+        return repo_group_to_perm
+
+    def _create_group(self, group_name):
+        """
+        makes repository group on filesystem
+
+        :param repo_name:
+        :param parent_id:
+        """
+
+        create_path = os.path.join(self.repos_path, group_name)
+        log.debug('creating new group in %s' % create_path)
+
+        if os.path.isdir(create_path):
+            raise Exception('That directory already exists !')
+
+        os.makedirs(create_path)
+        log.debug('Created group in %s' % create_path)
+
+    def _rename_group(self, old, new):
+        """
+        Renames a group on filesystem
+
+        :param group_name:
+        """
+
+        if old == new:
+            log.debug('skipping group rename')
+            return
+
+        log.debug('renaming repository group from %s to %s' % (old, new))
+
+        old_path = os.path.join(self.repos_path, old)
+        new_path = os.path.join(self.repos_path, new)
+
+        log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
+
+        if os.path.isdir(new_path):
+            raise Exception('Was trying to rename to already '
+                            'existing dir %s' % new_path)
+        shutil.move(old_path, new_path)
+
+    def _delete_group(self, group, force_delete=False):
+        """
+        Deletes a group from a filesystem
+
+        :param group: instance of group from database
+        :param force_delete: use shutil rmtree to remove all objects
+        """
+        paths = group.full_path.split(RepoGroup.url_sep())
+        paths = os.sep.join(paths)
+
+        rm_path = os.path.join(self.repos_path, paths)
+        log.info("Removing group %s" % (rm_path))
+        # delete only if that path really exists
+        if os.path.isdir(rm_path):
+            if force_delete:
+                shutil.rmtree(rm_path)
+            else:
+                #archive that group`
+                _now = datetime.datetime.now()
+                _ms = str(_now.microsecond).rjust(6, '0')
+                _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
+                                          group.name)
+                shutil.move(rm_path, os.path.join(self.repos_path, _d))
+
+    def create(self, group_name, group_description, owner, parent=None,
+               just_db=False, copy_permissions=False):
+        try:
+            user = self._get_user(owner)
+            parent_group = self._get_repo_group(parent)
+            new_repo_group = RepoGroup()
+            new_repo_group.user = user
+            new_repo_group.group_description = group_description or group_name
+            new_repo_group.parent_group = parent_group
+            new_repo_group.group_name = new_repo_group.get_new_name(group_name)
+
+            self.sa.add(new_repo_group)
+
+            # create an ADMIN permission for owner except if we're super admin,
+            # later owner should go into the owner field of groups
+            if not user.is_admin:
+                self.grant_user_permission(repo_group=new_repo_group,
+                                           user=owner, perm='group.admin')
+
+            if parent_group and copy_permissions:
+                # copy permissions from parent
+                user_perms = UserRepoGroupToPerm.query() \
+                    .filter(UserRepoGroupToPerm.group == parent_group).all()
+
+                group_perms = UserGroupRepoGroupToPerm.query() \
+                    .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
+
+                for perm in user_perms:
+                    # don't copy over the permission for user who is creating
+                    # this group, if he is not super admin he get's admin
+                    # permission set above
+                    if perm.user != user or user.is_admin:
+                        UserRepoGroupToPerm.create(perm.user, new_repo_group, perm.permission)
+
+                for perm in group_perms:
+                    UserGroupRepoGroupToPerm.create(perm.users_group, new_repo_group, perm.permission)
+            else:
+                perm_obj = self._create_default_perms(new_repo_group)
+                self.sa.add(perm_obj)
+
+            if not just_db:
+                # we need to flush here, in order to check if database won't
+                # throw any exceptions, create filesystem dirs at the very end
+                self.sa.flush()
+                self._create_group(new_repo_group.group_name)
+
+            return new_repo_group
+        except Exception:
+            log.error(traceback.format_exc())
+            raise
+
+    def _update_permissions(self, repo_group, perms_new=None,
+                            perms_updates=None, recursive=None,
+                            check_perms=True):
+        from rhodecode.model.repo import RepoModel
+        from rhodecode.lib.auth import HasUserGroupPermissionAny
+
+        if not perms_new:
+            perms_new = []
+        if not perms_updates:
+            perms_updates = []
+
+        def _set_perm_user(obj, user, perm):
+            if isinstance(obj, RepoGroup):
+                self.grant_user_permission(repo_group=obj, user=user, perm=perm)
+            elif isinstance(obj, Repository):
+                # private repos will not allow to change the default permissions
+                # using recursive mode
+                if obj.private and user == User.DEFAULT_USER:
+                    return
+
+                # we set group permission but we have to switch to repo
+                # permission
+                perm = perm.replace('group.', 'repository.')
+                RepoModel().grant_user_permission(
+                    repo=obj, user=user, perm=perm
+                )
+
+        def _set_perm_group(obj, users_group, perm):
+            if isinstance(obj, RepoGroup):
+                self.grant_user_group_permission(repo_group=obj,
+                                                  group_name=users_group,
+                                                  perm=perm)
+            elif isinstance(obj, Repository):
+                # we set group permission but we have to switch to repo
+                # permission
+                perm = perm.replace('group.', 'repository.')
+                RepoModel().grant_user_group_permission(
+                    repo=obj, group_name=users_group, perm=perm
+                )
+
+        # start updates
+        updates = []
+        log.debug('Now updating permissions for %s in recursive mode:%s'
+                  % (repo_group, recursive))
+
+        for obj in repo_group.recursive_groups_and_repos():
+            # iterated obj is an instance of a repos group or repository in
+            # that group, recursive option can be: none, repos, groups, all
+            if recursive == 'all':
+                obj = obj
+            elif recursive == 'repos':
+                # skip groups, other than this one
+                if isinstance(obj, RepoGroup) and not obj == repo_group:
+                    continue
+            elif recursive == 'groups':
+                # skip repos
+                if isinstance(obj, Repository):
+                    continue
+            else:  # recursive == 'none': # DEFAULT don't apply to iterated objects
+                obj = repo_group
+                # also we do a break at the end of this loop.
+
+            # update permissions
+            for member, perm, member_type in perms_updates:
+                ## set for user
+                if member_type == 'user':
+                    # this updates also current one if found
+                    _set_perm_user(obj, user=member, perm=perm)
+                ## set for user group
+                else:
+                    #check if we have permissions to alter this usergroup
+                    req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
+                    if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
+                        _set_perm_group(obj, users_group=member, perm=perm)
+            # set new permissions
+            for member, perm, member_type in perms_new:
+                if member_type == 'user':
+                    _set_perm_user(obj, user=member, perm=perm)
+                else:
+                    #check if we have permissions to alter this usergroup
+                    req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
+                    if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
+                        _set_perm_group(obj, users_group=member, perm=perm)
+            updates.append(obj)
+            # if it's not recursive call for all,repos,groups
+            # break the loop and don't proceed with other changes
+            if recursive not in ['all', 'repos', 'groups']:
+                break
+
+        return updates
+
+    def update(self, repo_group, form_data):
+
+        try:
+            repo_group = self._get_repo_group(repo_group)
+            old_path = repo_group.full_path
+
+            # change properties
+            repo_group.group_description = form_data['group_description']
+            repo_group.group_parent_id = form_data['group_parent_id']
+            repo_group.enable_locking = form_data['enable_locking']
+
+            repo_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
+            repo_group.group_name = repo_group.get_new_name(form_data['group_name'])
+            new_path = repo_group.full_path
+            self.sa.add(repo_group)
+
+            # iterate over all members of this groups and do fixes
+            # set locking if given
+            # if obj is a repoGroup also fix the name of the group according
+            # to the parent
+            # if obj is a Repo fix it's name
+            # this can be potentially heavy operation
+            for obj in repo_group.recursive_groups_and_repos():
+                #set the value from it's parent
+                obj.enable_locking = repo_group.enable_locking
+                if isinstance(obj, RepoGroup):
+                    new_name = obj.get_new_name(obj.name)
+                    log.debug('Fixing group %s to new name %s' \
+                                % (obj.group_name, new_name))
+                    obj.group_name = new_name
+                elif isinstance(obj, Repository):
+                    # we need to get all repositories from this new group and
+                    # rename them accordingly to new group path
+                    new_name = obj.get_new_name(obj.just_name)
+                    log.debug('Fixing repo %s to new name %s' \
+                                % (obj.repo_name, new_name))
+                    obj.repo_name = new_name
+                self.sa.add(obj)
+
+            self._rename_group(old_path, new_path)
+
+            return repo_group
+        except Exception:
+            log.error(traceback.format_exc())
+            raise
+
+    def delete(self, repo_group, force_delete=False):
+        repo_group = self._get_repo_group(repo_group)
+        try:
+            self.sa.delete(repo_group)
+            self._delete_group(repo_group, force_delete)
+        except Exception:
+            log.error('Error removing repo_group %s' % repo_group)
+            raise
+
+    def add_permission(self, repo_group, obj, obj_type, perm, recursive):
+        from rhodecode.model.repo import RepoModel
+        repo_group = self._get_repo_group(repo_group)
+        perm = self._get_perm(perm)
+
+        for el in repo_group.recursive_groups_and_repos():
+            # iterated obj is an instance of a repos group or repository in
+            # that group, recursive option can be: none, repos, groups, all
+            if recursive == 'all':
+                el = el
+            elif recursive == 'repos':
+                # skip groups, other than this one
+                if isinstance(el, RepoGroup) and not el == repo_group:
+                    continue
+            elif recursive == 'groups':
+                # skip repos
+                if isinstance(el, Repository):
+                    continue
+            else:  # recursive == 'none': # DEFAULT don't apply to iterated objects
+                el = repo_group
+                # also we do a break at the end of this loop.
+
+            if isinstance(el, RepoGroup):
+                if obj_type == 'user':
+                    RepoGroupModel().grant_user_permission(el, user=obj, perm=perm)
+                elif obj_type == 'user_group':
+                    RepoGroupModel().grant_user_group_permission(el, group_name=obj, perm=perm)
+                else:
+                    raise Exception('undefined object type %s' % obj_type)
+            elif isinstance(el, Repository):
+                # for repos we need to hotfix the name of permission
+                _perm = perm.permission_name.replace('group.', 'repository.')
+                if obj_type == 'user':
+                    RepoModel().grant_user_permission(el, user=obj, perm=_perm)
+                elif obj_type == 'user_group':
+                    RepoModel().grant_user_group_permission(el, group_name=obj, perm=_perm)
+                else:
+                    raise Exception('undefined object type %s' % obj_type)
+            else:
+                raise Exception('el should be instance of Repository or '
+                                'RepositoryGroup got %s instead' % type(el))
+
+            # if it's not recursive call for all,repos,groups
+            # break the loop and don't proceed with other changes
+            if recursive not in ['all', 'repos', 'groups']:
+                break
+
+    def delete_permission(self, repo_group, obj, obj_type, recursive):
+        """
+        Revokes permission for repo_group for given obj(user or users_group),
+        obj_type can be user or user group
+
+        :param repo_group:
+        :param obj: user or user group id
+        :param obj_type: user or user group type
+        :param recursive: recurse to all children of group
+        """
+        from rhodecode.model.repo import RepoModel
+        repo_group = self._get_repo_group(repo_group)
+
+        for el in repo_group.recursive_groups_and_repos():
+            # iterated obj is an instance of a repos group or repository in
+            # that group, recursive option can be: none, repos, groups, all
+            if recursive == 'all':
+                el = el
+            elif recursive == 'repos':
+                # skip groups, other than this one
+                if isinstance(el, RepoGroup) and not el == repo_group:
+                    continue
+            elif recursive == 'groups':
+                # skip repos
+                if isinstance(el, Repository):
+                    continue
+            else:  # recursive == 'none': # DEFAULT don't apply to iterated objects
+                el = repo_group
+                # also we do a break at the end of this loop.
+
+            if isinstance(el, RepoGroup):
+                if obj_type == 'user':
+                    RepoGroupModel().revoke_user_permission(el, user=obj)
+                elif obj_type == 'user_group':
+                    RepoGroupModel().revoke_user_group_permission(el, group_name=obj)
+                else:
+                    raise Exception('undefined object type %s' % obj_type)
+            elif isinstance(el, Repository):
+                if obj_type == 'user':
+                    RepoModel().revoke_user_permission(el, user=obj)
+                elif obj_type == 'user_group':
+                    RepoModel().revoke_user_group_permission(el, group_name=obj)
+                else:
+                    raise Exception('undefined object type %s' % obj_type)
+            else:
+                raise Exception('el should be instance of Repository or '
+                                'RepositoryGroup got %s instead' % type(el))
+
+            # if it's not recursive call for all,repos,groups
+            # break the loop and don't proceed with other changes
+            if recursive not in ['all', 'repos', 'groups']:
+                break
+
+    def grant_user_permission(self, repo_group, user, perm):
+        """
+        Grant permission for user on given repository group, or update
+        existing one if found
+
+        :param repo_group: Instance of RepoGroup, repositories_group_id,
+            or repositories_group name
+        :param user: Instance of User, user_id or username
+        :param perm: Instance of Permission, or permission_name
+        """
+
+        repo_group = self._get_repo_group(repo_group)
+        user = self._get_user(user)
+        permission = self._get_perm(perm)
+
+        # check if we have that permission already
+        obj = self.sa.query(UserRepoGroupToPerm)\
+            .filter(UserRepoGroupToPerm.user == user)\
+            .filter(UserRepoGroupToPerm.group == repo_group)\
+            .scalar()
+        if obj is None:
+            # create new !
+            obj = UserRepoGroupToPerm()
+        obj.group = repo_group
+        obj.user = user
+        obj.permission = permission
+        self.sa.add(obj)
+        log.debug('Granted perm %s to %s on %s' % (perm, user, repo_group))
+        return obj
+
+    def revoke_user_permission(self, repo_group, user):
+        """
+        Revoke permission for user on given repository group
+
+        :param repo_group: Instance of RepoGroup, repositories_group_id,
+            or repositories_group name
+        :param user: Instance of User, user_id or username
+        """
+
+        repo_group = self._get_repo_group(repo_group)
+        user = self._get_user(user)
+
+        obj = self.sa.query(UserRepoGroupToPerm)\
+            .filter(UserRepoGroupToPerm.user == user)\
+            .filter(UserRepoGroupToPerm.group == repo_group)\
+            .scalar()
+        if obj:
+            self.sa.delete(obj)
+            log.debug('Revoked perm on %s on %s' % (repo_group, user))
+
+    def grant_user_group_permission(self, repo_group, group_name, perm):
+        """
+        Grant permission for user group on given repository group, or update
+        existing one if found
+
+        :param repo_group: Instance of RepoGroup, repositories_group_id,
+            or repositories_group name
+        :param group_name: Instance of UserGroup, users_group_id,
+            or user group name
+        :param perm: Instance of Permission, or permission_name
+        """
+        repo_group = self._get_repo_group(repo_group)
+        group_name = self._get_user_group(group_name)
+        permission = self._get_perm(perm)
+
+        # check if we have that permission already
+        obj = self.sa.query(UserGroupRepoGroupToPerm)\
+            .filter(UserGroupRepoGroupToPerm.group == repo_group)\
+            .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
+            .scalar()
+
+        if obj is None:
+            # create new
+            obj = UserGroupRepoGroupToPerm()
+
+        obj.group = repo_group
+        obj.users_group = group_name
+        obj.permission = permission
+        self.sa.add(obj)
+        log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo_group))
+        return obj
+
+    def revoke_user_group_permission(self, repo_group, group_name):
+        """
+        Revoke permission for user group on given repository group
+
+        :param repo_group: Instance of RepoGroup, repositories_group_id,
+            or repositories_group name
+        :param group_name: Instance of UserGroup, users_group_id,
+            or user group name
+        """
+        repo_group = self._get_repo_group(repo_group)
+        group_name = self._get_user_group(group_name)
+
+        obj = self.sa.query(UserGroupRepoGroupToPerm)\
+            .filter(UserGroupRepoGroupToPerm.group == repo_group)\
+            .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
+            .scalar()
+        if obj:
+            self.sa.delete(obj)
+            log.debug('Revoked perm to %s on %s' % (repo_group, group_name))
--- a/rhodecode/model/repo_permission.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/repo_permission.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.users_group
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    repository permission model for RhodeCode
-
-    :created_on: Oct 1, 2011
-    :author: nvinot, marcink
-    :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -23,6 +11,15 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.users_group
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+repository permission model for RhodeCode
+
+:created_on: Oct 1, 2011
+:author: nvinot, marcink
+"""
 
 import logging
 from rhodecode.model import BaseModel
--- a/rhodecode/model/repos_group.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,443 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    rhodecode.model.user_group
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    repo group model for RhodeCode
-
-    :created_on: Jan 25, 2011
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# 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 os
-import logging
-import traceback
-import shutil
-import datetime
-
-from rhodecode.lib.utils2 import LazyProperty
-
-from rhodecode.model import BaseModel
-from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
-    User, Permission, UserGroupRepoGroupToPerm, UserGroup, Repository
-
-log = logging.getLogger(__name__)
-
-
-class ReposGroupModel(BaseModel):
-
-    cls = RepoGroup
-
-    def _get_user_group(self, users_group):
-        return self._get_instance(UserGroup, users_group,
-                                  callback=UserGroup.get_by_group_name)
-
-    def _get_repo_group(self, repos_group):
-        return self._get_instance(RepoGroup, repos_group,
-                                  callback=RepoGroup.get_by_group_name)
-
-    @LazyProperty
-    def repos_path(self):
-        """
-        Gets the repositories root path from database
-        """
-
-        q = RhodeCodeUi.get_by_key('/')
-        return q.ui_value
-
-    def _create_default_perms(self, new_group):
-        # create default permission
-        default_perm = 'group.read'
-        def_user = User.get_default_user()
-        for p in def_user.user_perms:
-            if p.permission.permission_name.startswith('group.'):
-                default_perm = p.permission.permission_name
-                break
-
-        repo_group_to_perm = UserRepoGroupToPerm()
-        repo_group_to_perm.permission = Permission.get_by_key(default_perm)
-
-        repo_group_to_perm.group = new_group
-        repo_group_to_perm.user_id = def_user.user_id
-        return repo_group_to_perm
-
-    def __create_group(self, group_name):
-        """
-        makes repository group on filesystem
-
-        :param repo_name:
-        :param parent_id:
-        """
-
-        create_path = os.path.join(self.repos_path, group_name)
-        log.debug('creating new group in %s' % create_path)
-
-        if os.path.isdir(create_path):
-            raise Exception('That directory already exists !')
-
-        os.makedirs(create_path)
-
-    def __rename_group(self, old, new):
-        """
-        Renames a group on filesystem
-
-        :param group_name:
-        """
-
-        if old == new:
-            log.debug('skipping group rename')
-            return
-
-        log.debug('renaming repository group from %s to %s' % (old, new))
-
-        old_path = os.path.join(self.repos_path, old)
-        new_path = os.path.join(self.repos_path, new)
-
-        log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
-
-        if os.path.isdir(new_path):
-            raise Exception('Was trying to rename to already '
-                            'existing dir %s' % new_path)
-        shutil.move(old_path, new_path)
-
-    def __delete_group(self, group, force_delete=False):
-        """
-        Deletes a group from a filesystem
-
-        :param group: instance of group from database
-        :param force_delete: use shutil rmtree to remove all objects
-        """
-        paths = group.full_path.split(RepoGroup.url_sep())
-        paths = os.sep.join(paths)
-
-        rm_path = os.path.join(self.repos_path, paths)
-        log.info("Removing group %s" % (rm_path))
-        # delete only if that path really exists
-        if os.path.isdir(rm_path):
-            if force_delete:
-                shutil.rmtree(rm_path)
-            else:
-                #archive that group`
-                _now = datetime.datetime.now()
-                _ms = str(_now.microsecond).rjust(6, '0')
-                _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
-                                          group.name)
-                shutil.move(rm_path, os.path.join(self.repos_path, _d))
-
-    def create(self, group_name, group_description, owner, parent=None, just_db=False):
-        try:
-            user = self._get_user(owner)
-            new_repos_group = RepoGroup()
-            new_repos_group.user = user
-            new_repos_group.group_description = group_description or group_name
-            new_repos_group.parent_group = self._get_repo_group(parent)
-            new_repos_group.group_name = new_repos_group.get_new_name(group_name)
-
-            self.sa.add(new_repos_group)
-            perm_obj = self._create_default_perms(new_repos_group)
-            self.sa.add(perm_obj)
-
-            #create an ADMIN permission for owner except if we're super admin,
-            #later owner should go into the owner field of groups
-            if not user.is_admin:
-                self.grant_user_permission(repos_group=new_repos_group,
-                                           user=owner, perm='group.admin')
-
-            if not just_db:
-                # we need to flush here, in order to check if database won't
-                # throw any exceptions, create filesystem dirs at the very end
-                self.sa.flush()
-                self.__create_group(new_repos_group.group_name)
-
-            return new_repos_group
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
-
-    def _update_permissions(self, repos_group, perms_new=None,
-                            perms_updates=None, recursive=False,
-                            check_perms=True):
-        from rhodecode.model.repo import RepoModel
-        from rhodecode.lib.auth import HasUserGroupPermissionAny
-
-        if not perms_new:
-            perms_new = []
-        if not perms_updates:
-            perms_updates = []
-
-        def _set_perm_user(obj, user, perm):
-            if isinstance(obj, RepoGroup):
-                self.grant_user_permission(
-                    repos_group=obj, user=user, perm=perm
-                )
-            elif isinstance(obj, Repository):
-                # private repos will not allow to change the default permissions
-                # using recursive mode
-                if obj.private and user == User.DEFAULT_USER:
-                    return
-
-                # we set group permission but we have to switch to repo
-                # permission
-                perm = perm.replace('group.', 'repository.')
-                RepoModel().grant_user_permission(
-                    repo=obj, user=user, perm=perm
-                )
-
-        def _set_perm_group(obj, users_group, perm):
-            if isinstance(obj, RepoGroup):
-                self.grant_users_group_permission(
-                    repos_group=obj, group_name=users_group, perm=perm
-                )
-            elif isinstance(obj, Repository):
-                # we set group permission but we have to switch to repo
-                # permission
-                perm = perm.replace('group.', 'repository.')
-                RepoModel().grant_users_group_permission(
-                    repo=obj, group_name=users_group, perm=perm
-                )
-
-        # start updates
-        updates = []
-        log.debug('Now updating permissions for %s in recursive mode:%s'
-                  % (repos_group, recursive))
-
-        for obj in repos_group.recursive_groups_and_repos():
-            #obj is an instance of a group or repositories in that group
-            if not recursive:
-                obj = repos_group
-
-            # update permissions
-            for member, perm, member_type in perms_updates:
-                ## set for user
-                if member_type == 'user':
-                    # this updates also current one if found
-                    _set_perm_user(obj, user=member, perm=perm)
-                ## set for user group
-                else:
-                    #check if we have permissions to alter this usergroup
-                    req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
-                    if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
-                        _set_perm_group(obj, users_group=member, perm=perm)
-            # set new permissions
-            for member, perm, member_type in perms_new:
-                if member_type == 'user':
-                    _set_perm_user(obj, user=member, perm=perm)
-                else:
-                    #check if we have permissions to alter this usergroup
-                    req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
-                    if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
-                        _set_perm_group(obj, users_group=member, perm=perm)
-            updates.append(obj)
-            #if it's not recursive call
-            # break the loop and don't proceed with other changes
-            if not recursive:
-                break
-        return updates
-
-    def update(self, repos_group, form_data):
-
-        try:
-            repos_group = self._get_repo_group(repos_group)
-            old_path = repos_group.full_path
-
-            # change properties
-            repos_group.group_description = form_data['group_description']
-            repos_group.group_parent_id = form_data['group_parent_id']
-            repos_group.enable_locking = form_data['enable_locking']
-
-            repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
-            repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
-            new_path = repos_group.full_path
-            self.sa.add(repos_group)
-
-            # iterate over all members of this groups and do fixes
-            # set locking if given
-            # if obj is a repoGroup also fix the name of the group according
-            # to the parent
-            # if obj is a Repo fix it's name
-            # this can be potentially heavy operation
-            for obj in repos_group.recursive_groups_and_repos():
-                #set the value from it's parent
-                obj.enable_locking = repos_group.enable_locking
-                if isinstance(obj, RepoGroup):
-                    new_name = obj.get_new_name(obj.name)
-                    log.debug('Fixing group %s to new name %s' \
-                                % (obj.group_name, new_name))
-                    obj.group_name = new_name
-                elif isinstance(obj, Repository):
-                    # we need to get all repositories from this new group and
-                    # rename them accordingly to new group path
-                    new_name = obj.get_new_name(obj.just_name)
-                    log.debug('Fixing repo %s to new name %s' \
-                                % (obj.repo_name, new_name))
-                    obj.repo_name = new_name
-                self.sa.add(obj)
-
-            self.__rename_group(old_path, new_path)
-
-            return repos_group
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
-
-    def delete(self, repos_group, force_delete=False):
-        repos_group = self._get_repo_group(repos_group)
-        try:
-            self.sa.delete(repos_group)
-            self.__delete_group(repos_group, force_delete)
-        except Exception:
-            log.error('Error removing repos_group %s' % repos_group)
-            raise
-
-    def delete_permission(self, repos_group, obj, obj_type, recursive):
-        """
-        Revokes permission for repos_group for given obj(user or users_group),
-        obj_type can be user or user group
-
-        :param repos_group:
-        :param obj: user or user group id
-        :param obj_type: user or user group type
-        :param recursive: recurse to all children of group
-        """
-        from rhodecode.model.repo import RepoModel
-        repos_group = self._get_repo_group(repos_group)
-
-        for el in repos_group.recursive_groups_and_repos():
-            if not recursive:
-                # if we don't recurse set the permission on only the top level
-                # object
-                el = repos_group
-
-            if isinstance(el, RepoGroup):
-                if obj_type == 'user':
-                    ReposGroupModel().revoke_user_permission(el, user=obj)
-                elif obj_type == 'users_group':
-                    ReposGroupModel().revoke_users_group_permission(el, group_name=obj)
-                else:
-                    raise Exception('undefined object type %s' % obj_type)
-            elif isinstance(el, Repository):
-                if obj_type == 'user':
-                    RepoModel().revoke_user_permission(el, user=obj)
-                elif obj_type == 'users_group':
-                    RepoModel().revoke_users_group_permission(el, group_name=obj)
-                else:
-                    raise Exception('undefined object type %s' % obj_type)
-
-            #if it's not recursive call
-            # break the loop and don't proceed with other changes
-            if not recursive:
-                break
-
-    def grant_user_permission(self, repos_group, user, perm):
-        """
-        Grant permission for user on given repository group, or update
-        existing one if found
-
-        :param repos_group: Instance of ReposGroup, repositories_group_id,
-            or repositories_group name
-        :param user: Instance of User, user_id or username
-        :param perm: Instance of Permission, or permission_name
-        """
-
-        repos_group = self._get_repo_group(repos_group)
-        user = self._get_user(user)
-        permission = self._get_perm(perm)
-
-        # check if we have that permission already
-        obj = self.sa.query(UserRepoGroupToPerm)\
-            .filter(UserRepoGroupToPerm.user == user)\
-            .filter(UserRepoGroupToPerm.group == repos_group)\
-            .scalar()
-        if obj is None:
-            # create new !
-            obj = UserRepoGroupToPerm()
-        obj.group = repos_group
-        obj.user = user
-        obj.permission = permission
-        self.sa.add(obj)
-        log.debug('Granted perm %s to %s on %s' % (perm, user, repos_group))
-
-    def revoke_user_permission(self, repos_group, user):
-        """
-        Revoke permission for user on given repository group
-
-        :param repos_group: Instance of ReposGroup, repositories_group_id,
-            or repositories_group name
-        :param user: Instance of User, user_id or username
-        """
-
-        repos_group = self._get_repo_group(repos_group)
-        user = self._get_user(user)
-
-        obj = self.sa.query(UserRepoGroupToPerm)\
-            .filter(UserRepoGroupToPerm.user == user)\
-            .filter(UserRepoGroupToPerm.group == repos_group)\
-            .scalar()
-        if obj:
-            self.sa.delete(obj)
-            log.debug('Revoked perm on %s on %s' % (repos_group, user))
-
-    def grant_users_group_permission(self, repos_group, group_name, perm):
-        """
-        Grant permission for user group on given repository group, or update
-        existing one if found
-
-        :param repos_group: Instance of ReposGroup, repositories_group_id,
-            or repositories_group name
-        :param group_name: Instance of UserGroup, users_group_id,
-            or user group name
-        :param perm: Instance of Permission, or permission_name
-        """
-        repos_group = self._get_repo_group(repos_group)
-        group_name = self._get_user_group(group_name)
-        permission = self._get_perm(perm)
-
-        # check if we have that permission already
-        obj = self.sa.query(UserGroupRepoGroupToPerm)\
-            .filter(UserGroupRepoGroupToPerm.group == repos_group)\
-            .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
-            .scalar()
-
-        if obj is None:
-            # create new
-            obj = UserGroupRepoGroupToPerm()
-
-        obj.group = repos_group
-        obj.users_group = group_name
-        obj.permission = permission
-        self.sa.add(obj)
-        log.debug('Granted perm %s to %s on %s' % (perm, group_name, repos_group))
-
-    def revoke_users_group_permission(self, repos_group, group_name):
-        """
-        Revoke permission for user group on given repository group
-
-        :param repos_group: Instance of ReposGroup, repositories_group_id,
-            or repositories_group name
-        :param group_name: Instance of UserGroup, users_group_id,
-            or user group name
-        """
-        repos_group = self._get_repo_group(repos_group)
-        group_name = self._get_user_group(group_name)
-
-        obj = self.sa.query(UserGroupRepoGroupToPerm)\
-            .filter(UserGroupRepoGroupToPerm.group == repos_group)\
-            .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
-            .scalar()
-        if obj:
-            self.sa.delete(obj)
-            log.debug('Revoked perm to %s on %s' % (repos_group, group_name))
--- a/rhodecode/model/scm.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/scm.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.scm
-    ~~~~~~~~~~~~~~~~~~~
-
-    Scm model for RhodeCode
-
-    :created_on: Apr 9, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.scm
+~~~~~~~~~~~~~~~~~~~
+
+Scm model for RhodeCode
+
+:created_on: Apr 9, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 from __future__ import with_statement
 import os
 import re
@@ -46,7 +47,7 @@
 from rhodecode.lib import helpers as h
 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\
     _set_extras
-from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny,\
+from rhodecode.lib.auth import HasRepoPermissionAny, HasRepoGroupPermissionAny,\
     HasUserGroupPermissionAny
 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
     action_logger
@@ -154,19 +155,16 @@
                 *self.perm_set)(dbr.repo_name, 'get repo check'):
                 continue
 
-            tmp_d = {}
-            tmp_d['name'] = dbr.repo_name
-            tmp_d['name_sort'] = tmp_d['name'].lower()
-            tmp_d['raw_name'] = tmp_d['name'].lower()
-            tmp_d['description'] = dbr.description
-            tmp_d['description_sort'] = tmp_d['description'].lower()
-            tmp_d['dbrepo'] = dbr.get_dict()
-            tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
+            tmp_d = {
+                'name': dbr.repo_name,
+                'dbrepo': dbr.get_dict(),
+                'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
+            }
             yield tmp_d
 
 
 class _PermCheckIterator(object):
-    def __init__(self, obj_list, obj_attr, perm_set, perm_checker):
+    def __init__(self, obj_list, obj_attr, perm_set, perm_checker, extra_kwargs=None):
         """
         Creates iterator from given list of objects, additionally
         checking permission for them from perm_set var
@@ -180,6 +178,7 @@
         self.obj_attr = obj_attr
         self.perm_set = perm_set
         self.perm_checker = perm_checker
+        self.extra_kwargs = extra_kwargs or {}
 
     def __len__(self):
         return len(self.obj_list)
@@ -191,7 +190,8 @@
         for db_obj in self.obj_list:
             # check permission at this level
             name = getattr(db_obj, self.obj_attr, None)
-            if not self.perm_checker(*self.perm_set)(name, self.__class__.__name__):
+            if not self.perm_checker(*self.perm_set)(
+                    name, self.__class__.__name__, **self.extra_kwargs):
                 continue
 
             yield db_obj
@@ -199,35 +199,38 @@
 
 class RepoList(_PermCheckIterator):
 
-    def __init__(self, db_repo_list, perm_set=None):
+    def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
         if not perm_set:
             perm_set = ['repository.read', 'repository.write', 'repository.admin']
 
         super(RepoList, self).__init__(obj_list=db_repo_list,
                     obj_attr='repo_name', perm_set=perm_set,
-                    perm_checker=HasRepoPermissionAny)
+                    perm_checker=HasRepoPermissionAny,
+                    extra_kwargs=extra_kwargs)
 
 
 class RepoGroupList(_PermCheckIterator):
 
-    def __init__(self, db_repo_group_list, perm_set=None):
+    def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
         if not perm_set:
             perm_set = ['group.read', 'group.write', 'group.admin']
 
         super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
                     obj_attr='group_name', perm_set=perm_set,
-                    perm_checker=HasReposGroupPermissionAny)
+                    perm_checker=HasRepoGroupPermissionAny,
+                    extra_kwargs=extra_kwargs)
 
 
 class UserGroupList(_PermCheckIterator):
 
-    def __init__(self, db_user_group_list, perm_set=None):
+    def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
         if not perm_set:
             perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
 
         super(UserGroupList, self).__init__(obj_list=db_user_group_list,
                     obj_attr='users_group_name', perm_set=perm_set,
-                    perm_checker=HasUserGroupPermissionAny)
+                    perm_checker=HasUserGroupPermissionAny,
+                    extra_kwargs=extra_kwargs)
 
 
 class ScmModel(BaseModel):
@@ -322,19 +325,19 @@
 
         return repo_iter
 
-    def get_repos_groups(self, all_groups=None):
+    def get_repo_groups(self, all_groups=None):
         if all_groups is None:
             all_groups = RepoGroup.query()\
                 .filter(RepoGroup.group_parent_id == None).all()
         return [x for x in RepoGroupList(all_groups)]
 
-    def mark_for_invalidation(self, repo_name):
+    def mark_for_invalidation(self, repo_name, delete=False):
         """
         Mark caches of this repo invalid in the database.
 
         :param repo_name: the repo for which caches should be marked invalid
         """
-        CacheInvalidation.set_invalidate(repo_name)
+        CacheInvalidation.set_invalidate(repo_name, delete=delete)
         repo = Repository.get_by_repo_name(repo_name)
         if repo:
             repo.update_changeset_cache()
@@ -432,6 +435,10 @@
         fork = self.__get_repo(fork)
         if fork and repo.repo_id == fork.repo_id:
             raise Exception("Cannot set repository as fork of itself")
+
+        if fork and repo.repo_type != fork.repo_type:
+            raise RepositoryError("Cannot set repository as fork of repository with other type")
+
         repo.fork = fork
         self.sa.add(repo)
         return repo
@@ -562,6 +569,43 @@
                           revisions=[tip.raw_id])
         return tip
 
+    def _sanitize_path(self, f_path):
+        if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
+            raise NonRelativePathError('%s is not an relative path' % f_path)
+        if f_path:
+            f_path = os.path.normpath(f_path)
+        return f_path
+
+    def get_nodes(self, repo_name, revision, root_path='/', flat=True):
+        """
+        recursive walk in root dir and return a set of all path in that dir
+        based on repository walk function
+
+        :param repo_name: name of repository
+        :param revision: revision for which to list nodes
+        :param root_path: root path to list
+        :param flat: return as a list, if False returns a dict with decription
+
+        """
+        _files = list()
+        _dirs = list()
+        try:
+            _repo = self.__get_repo(repo_name)
+            changeset = _repo.scm_instance.get_changeset(revision)
+            root_path = root_path.lstrip('/')
+            for topnode, dirs, files in changeset.walk(root_path):
+                for f in files:
+                    _files.append(f.path if flat else {"name": f.path,
+                                                       "type": "file"})
+                for d in dirs:
+                    _dirs.append(d.path if flat else {"name": d.path,
+                                                      "type": "dir"})
+        except RepositoryError:
+            log.debug(traceback.format_exc())
+            raise
+
+        return _dirs, _files
+
     def create_nodes(self, user, repo, message, nodes, parent_cs=None,
                      author=None, trigger_push_hook=True):
         """
@@ -583,10 +627,7 @@
 
         processed_nodes = []
         for f_path in nodes:
-            if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
-                raise NonRelativePathError('%s is not an relative path' % f_path)
-            if f_path:
-                f_path = os.path.normpath(f_path)
+            f_path = self._sanitize_path(f_path)
             content = nodes[f_path]['content']
             f_path = safe_str(f_path)
             # decoding here will force that we have proper encoded values
@@ -634,35 +675,121 @@
                               revisions=[tip.raw_id])
         return tip
 
-    def get_nodes(self, repo_name, revision, root_path='/', flat=True):
-        """
-        recursive walk in root dir and return a set of all path in that dir
-        based on repository walk function
+    def update_nodes(self, user, repo, message, nodes, parent_cs=None,
+                     author=None, trigger_push_hook=True):
+        user = self._get_user(user)
+        scm_instance = repo.scm_instance_no_cache()
+
+        message = safe_unicode(message)
+        commiter = user.full_contact
+        author = safe_unicode(author) if author else commiter
+
+        imc_class = self._get_IMC_module(scm_instance.alias)
+        imc = imc_class(scm_instance)
+
+        if not parent_cs:
+            parent_cs = EmptyChangeset(alias=scm_instance.alias)
+
+        if isinstance(parent_cs, EmptyChangeset):
+            # EmptyChangeset means we we're editing empty repository
+            parents = None
+        else:
+            parents = [parent_cs]
+
+        # add multiple nodes
+        for _filename, data in nodes.items():
+            # new filename, can be renamed from the old one
+            filename = self._sanitize_path(data['filename'])
+            old_filename = self._sanitize_path(_filename)
+            content = data['content']
 
-        :param repo_name: name of repository
-        :param revision: revision for which to list nodes
-        :param root_path: root path to list
-        :param flat: return as a list, if False returns a dict with decription
+            filenode = FileNode(old_filename, content=content)
+            op = data['op']
+            if op == 'add':
+                imc.add(filenode)
+            elif op == 'del':
+                imc.remove(filenode)
+            elif op == 'mod':
+                if filename != old_filename:
+                    #TODO: handle renames, needs vcs lib changes
+                    imc.remove(filenode)
+                    imc.add(FileNode(filename, content=content))
+                else:
+                    imc.change(filenode)
 
+        # commit changes
+        tip = imc.commit(message=message,
+                         author=author,
+                         parents=parents,
+                         branch=parent_cs.branch)
+
+        self.mark_for_invalidation(repo.repo_name)
+        if trigger_push_hook:
+            self._handle_push(scm_instance,
+                              username=user.username,
+                              action='push_local',
+                              repo_name=repo.repo_name,
+                              revisions=[tip.raw_id])
+
+    def delete_nodes(self, user, repo, message, nodes, parent_cs=None,
+                     author=None, trigger_push_hook=True):
         """
-        _files = list()
-        _dirs = list()
-        try:
-            _repo = self.__get_repo(repo_name)
-            changeset = _repo.scm_instance.get_changeset(revision)
-            root_path = root_path.lstrip('/')
-            for topnode, dirs, files in changeset.walk(root_path):
-                for f in files:
-                    _files.append(f.path if flat else {"name": f.path,
-                                                       "type": "file"})
-                for d in dirs:
-                    _dirs.append(d.path if flat else {"name": d.path,
-                                                      "type": "dir"})
-        except RepositoryError:
-            log.debug(traceback.format_exc())
-            raise
+        Deletes given multiple nodes into repo
+
+        :param user: RhodeCode User object or user_id, the commiter
+        :param repo: RhodeCode Repository object
+        :param message: commit message
+        :param nodes: mapping {filename:{'content':content},...}
+        :param parent_cs: parent changeset, can be empty than it's initial commit
+        :param author: author of commit, cna be different that commiter only for git
+        :param trigger_push_hook: trigger push hooks
+
+        :returns: new commited changeset after deletion
+        """
+
+        user = self._get_user(user)
+        scm_instance = repo.scm_instance_no_cache()
+
+        processed_nodes = []
+        for f_path in nodes:
+            f_path = self._sanitize_path(f_path)
+            # content can be empty but for compatabilty it allows same dicts
+            # structure as add_nodes
+            content = nodes[f_path].get('content')
+            processed_nodes.append((f_path, content))
+
+        message = safe_unicode(message)
+        commiter = user.full_contact
+        author = safe_unicode(author) if author else commiter
 
-        return _dirs, _files
+        IMC = self._get_IMC_module(scm_instance.alias)
+        imc = IMC(scm_instance)
+
+        if not parent_cs:
+            parent_cs = EmptyChangeset(alias=scm_instance.alias)
+
+        if isinstance(parent_cs, EmptyChangeset):
+            # EmptyChangeset means we we're editing empty repository
+            parents = None
+        else:
+            parents = [parent_cs]
+        # add multiple nodes
+        for path, content in processed_nodes:
+            imc.remove(FileNode(path, content=content))
+
+        tip = imc.commit(message=message,
+                         author=author,
+                         parents=parents,
+                         branch=parent_cs.branch)
+
+        self.mark_for_invalidation(repo.repo_name)
+        if trigger_push_hook:
+            self._handle_push(scm_instance,
+                              username=user.username,
+                              action='push_local',
+                              repo_name=repo.repo_name,
+                              revisions=[tip.raw_id])
+        return tip
 
     def get_unread_journal(self):
         return self.sa.query(UserLog).count()
@@ -678,25 +805,25 @@
         hist_l = []
         choices = []
         repo = self.__get_repo(repo)
-        hist_l.append(['tip', _('latest tip')])
-        choices.append('tip')
+        hist_l.append(['rev:tip', _('latest tip')])
+        choices.append('rev:tip')
         if not repo:
             return choices, hist_l
 
         repo = repo.scm_instance
 
-        branches_group = ([(k, k) for k, v in
+        branches_group = ([(u'branch:%s' % k, k) for k, v in
                            repo.branches.iteritems()], _("Branches"))
         hist_l.append(branches_group)
         choices.extend([x[0] for x in branches_group[0]])
 
         if repo.alias == 'hg':
-            bookmarks_group = ([(k, k) for k, v in
+            bookmarks_group = ([(u'book:%s' % k, k) for k, v in
                                 repo.bookmarks.iteritems()], _("Bookmarks"))
             hist_l.append(bookmarks_group)
             choices.extend([x[0] for x in bookmarks_group[0]])
 
-        tags_group = ([(k, k) for k, v in
+        tags_group = ([(u'tag:%s' % k, k) for k, v in
                        repo.tags.iteritems()], _("Tags"))
         hist_l.append(tags_group)
         choices.extend([x[0] for x in tags_group[0]])
--- a/rhodecode/model/user.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/user.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.model.user
-    ~~~~~~~~~~~~~~~~~~~~
-
-    users model for RhodeCode
-
-    :created_on: Apr 9, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,24 +11,32 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.user
+~~~~~~~~~~~~~~~~~~~~
+
+users model for RhodeCode
+
+:created_on: Apr 9, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 
 import logging
 import traceback
-import itertools
-import collections
 from pylons import url
 from pylons.i18n.translation import _
 
 from sqlalchemy.exc import DatabaseError
-from sqlalchemy.orm import joinedload
+
 
 from rhodecode.lib.utils2 import safe_unicode, generate_api_key, get_current_rhodecode_user
 from rhodecode.lib.caching_query import FromCache
 from rhodecode.model import BaseModel
-from rhodecode.model.db import User, Repository, Permission, \
-    UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \
-    Notification, RepoGroup, UserGroupRepoGroupToPerm, \
-    UserEmailMap, UserIpMap, UserGroupUserGroupToPerm, UserGroup
+from rhodecode.model.db import User, UserToPerm, Notification, \
+    UserEmailMap, UserIpMap
 from rhodecode.lib.exceptions import DefaultUserException, \
     UserOwnsReposException
 from rhodecode.model.meta import Session
@@ -47,8 +44,6 @@
 
 log = logging.getLogger(__name__)
 
-PERM_WEIGHTS = Permission.PERM_WEIGHTS
-
 
 class UserModel(BaseModel):
     cls = User
@@ -68,7 +63,8 @@
         if case_insensitive:
             user = self.sa.query(User).filter(User.username.ilike(username))
         else:
-            user = self.sa.query(User).filter(User.username == username)
+            user = self.sa.query(User)\
+                .filter(User.username == username)
         if cache:
             user = user.options(FromCache("sql_cache_short",
                                           "get_user_%s" % username))
@@ -93,7 +89,6 @@
         }
         # raises UserCreationError if it's not allowed
         check_allowed_create_user(user_data, cur_user)
-
         from rhodecode.lib.auth import get_crypt_password
         try:
             new_user = User()
@@ -114,8 +109,8 @@
             raise
 
     def create_or_update(self, username, password, email, firstname='',
-                         lastname='', active=True, admin=False, ldap_dn=None,
-                         cur_user=None):
+                         lastname='', active=True, admin=False,
+                         extern_type=None, extern_name=None, cur_user=None):
         """
         Creates a new instance if not found, or updates current one
 
@@ -127,13 +122,14 @@
         :param lastname:
         :param active:
         :param admin:
-        :param ldap_dn:
+        :param extern_name:
+        :param extern_type:
         :param cur_user:
         """
         if not cur_user:
             cur_user = getattr(get_current_rhodecode_user(), 'username', None)
 
-        from rhodecode.lib.auth import get_crypt_password
+        from rhodecode.lib.auth import get_crypt_password, check_password
         from rhodecode.lib.hooks import log_create_user, check_allowed_create_user
         user_data = {
             'username': username, 'password': password,
@@ -157,15 +153,24 @@
         try:
             new_user.username = username
             new_user.admin = admin
-            # set password only if creating an user or password is changed
-            if not edit or user.password != password:
-                new_user.password = get_crypt_password(password) if password else None
-                new_user.api_key = generate_api_key(username)
             new_user.email = email
             new_user.active = active
-            new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
+            new_user.extern_name = safe_unicode(extern_name) if extern_name else None
+            new_user.extern_type = safe_unicode(extern_type) if extern_type else None
             new_user.name = firstname
             new_user.lastname = lastname
+
+            if not edit:
+                new_user.api_key = generate_api_key(username)
+
+            # set password only if creating an user or password is changed
+            password_change = new_user.password and not check_password(password,
+                                                            new_user.password)
+            if not edit or password_change:
+                reason = 'new password' if edit else 'new user'
+                log.debug('Updating password reason=>%s' % (reason,))
+                new_user.password = get_crypt_password(password) if password else None
+
             self.sa.add(new_user)
 
             if not edit:
@@ -175,116 +180,13 @@
             log.error(traceback.format_exc())
             raise
 
-    def create_for_container_auth(self, username, attrs, cur_user=None):
-        """
-        Creates the given user if it's not already in the database
-
-        :param username:
-        :param attrs:
-        :param cur_user:
-        """
-        if not cur_user:
-            cur_user = getattr(get_current_rhodecode_user(), 'username', None)
-        if self.get_by_username(username, case_insensitive=True) is None:
-            # autogenerate email for container account without one
-            generate_email = lambda usr: '%s@container_auth.account' % usr
-            firstname = attrs['name']
-            lastname = attrs['lastname']
-            active = attrs.get('active', True)
-            email = attrs['email'] or generate_email(username)
-
-            from rhodecode.lib.hooks import log_create_user, check_allowed_create_user
-            user_data = {
-                'username': username, 'password': None,
-                'email': email, 'firstname': firstname, 'lastname': lastname,
-                'active': attrs.get('active', True), 'admin': False
-            }
-            # raises UserCreationError if it's not allowed
-            check_allowed_create_user(user_data, cur_user)
-
-            try:
-                new_user = User()
-                new_user.username = username
-                new_user.password = None
-                new_user.api_key = generate_api_key(username)
-                new_user.email = email
-                new_user.active = active
-                new_user.name = firstname
-                new_user.lastname = lastname
-
-                self.sa.add(new_user)
-                log_create_user(new_user.get_dict(), cur_user)
-                return new_user
-            except (DatabaseError,):
-                log.error(traceback.format_exc())
-                self.sa.rollback()
-                raise
-        log.debug('User %s already exists. Skipping creation of account'
-                  ' for container auth.', username)
-        return None
-
-    def create_ldap(self, username, password, user_dn, attrs, cur_user=None):
-        """
-        Checks if user is in database, if not creates this user marked
-        as ldap user
-
-        :param username:
-        :param password:
-        :param user_dn:
-        :param attrs:
-        :param cur_user:
-        """
-        if not cur_user:
-            cur_user = getattr(get_current_rhodecode_user(), 'username', None)
-        from rhodecode.lib.auth import get_crypt_password
-        log.debug('Checking for such ldap account in RhodeCode database')
-        if self.get_by_username(username, case_insensitive=True) is None:
-            # autogenerate email for container account without one
-            generate_email = lambda usr: '%s@ldap.account' % usr
-            password = get_crypt_password(password)
-            firstname = attrs['name']
-            lastname = attrs['lastname']
-            active = attrs.get('active', True)
-            email = attrs['email'] or generate_email(username)
-
-            from rhodecode.lib.hooks import log_create_user, check_allowed_create_user
-            user_data = {
-                'username': username, 'password': password,
-                'email': email, 'firstname': firstname, 'lastname': lastname,
-                'active': attrs.get('active', True), 'admin': False
-            }
-            # raises UserCreationError if it's not allowed
-            check_allowed_create_user(user_data, cur_user)
-
-            try:
-                new_user = User()
-                username = username.lower()
-                # add ldap account always lowercase
-                new_user.username = username
-                new_user.password = password
-                new_user.api_key = generate_api_key(username)
-                new_user.email = email
-                new_user.active = active
-                new_user.ldap_dn = safe_unicode(user_dn)
-                new_user.name = firstname
-                new_user.lastname = lastname
-                self.sa.add(new_user)
-
-                log_create_user(new_user.get_dict(), cur_user)
-                return new_user
-            except (DatabaseError,):
-                log.error(traceback.format_exc())
-                self.sa.rollback()
-                raise
-        log.debug('this %s user exists skipping creation of ldap account',
-                  username)
-        return None
-
     def create_registration(self, form_data):
         from rhodecode.model.notification import NotificationModel
 
         try:
             form_data['admin'] = False
+            form_data['extern_name'] = 'rhodecode'
+            form_data['extern_type'] = 'rhodecode'
             new_user = self.create(form_data)
 
             self.sa.add(new_user)
@@ -313,18 +215,19 @@
         from rhodecode.lib.auth import get_crypt_password
         try:
             user = self.get(user_id, cache=False)
-            if user.username == 'default':
+            if user.username == User.DEFAULT_USER:
                 raise DefaultUserException(
-                                _("You can't Edit this user since it's"
-                                  " crucial for entire application"))
+                                _("You can't Edit this user since it's "
+                                  "crucial for entire application"))
 
             for k, v in form_data.items():
                 if k in skip_attrs:
                     continue
                 if k == 'new_password' and v:
                     user.password = get_crypt_password(v)
-                    user.api_key = generate_api_key(user.username)
                 else:
+                    # old legacy thing orm models store firstname as name,
+                    # need proper refactor to username
                     if k == 'firstname':
                         k = 'name'
                     setattr(user, k, v)
@@ -337,7 +240,7 @@
         from rhodecode.lib.auth import get_crypt_password
         try:
             user = self._get_user(user)
-            if user.username == 'default':
+            if user.username == User.DEFAULT_USER:
                 raise DefaultUserException(
                     _("You can't Edit this user since it's"
                       " crucial for entire application")
@@ -346,7 +249,6 @@
             for k, v in kwargs.items():
                 if k == 'password' and v:
                     v = get_crypt_password(v)
-                    user.api_key = generate_api_key(user.username)
 
                 setattr(user, k, v)
             self.sa.add(user)
@@ -361,7 +263,7 @@
         user = self._get_user(user)
 
         try:
-            if user.username == 'default':
+            if user.username == User.DEFAULT_USER:
                 raise DefaultUserException(
                     _(u"You can't remove this user since it's"
                       " crucial for entire application")
@@ -418,7 +320,6 @@
                             auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
             if user:
                 user.password = auth.get_crypt_password(new_passwd)
-                user.api_key = auth.generate_api_key(user.username)
                 Session().add(user)
                 Session().commit()
                 log.info('change password for %s' % user_email)
@@ -441,7 +342,7 @@
 
         return True
 
-    def fill_data(self, auth_user, user_id=None, api_key=None):
+    def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
         """
         Fetches auth_user by user_id,or api_key if present.
         Fills auth_user attributes with those taken from database.
@@ -451,20 +352,25 @@
         :param auth_user: instance of user to set attributes
         :param user_id: user id to fetch by
         :param api_key: api key to fetch by
+        :param username: username to fetch by
         """
-        if user_id is None and api_key is None:
-            raise Exception('You need to pass user_id or api_key')
+        if user_id is None and api_key is None and username is None:
+            raise Exception('You need to pass user_id, api_key or username')
 
         try:
-            if api_key:
+            dbuser = None
+            if user_id:
+                dbuser = self.get(user_id)
+            elif api_key:
                 dbuser = self.get_by_api_key(api_key)
-            else:
-                dbuser = self.get(user_id)
+            elif username:
+                dbuser = self.get_by_username(username)
 
             if dbuser is not None and dbuser.active:
                 log.debug('filling %s data' % dbuser)
-                for k, v in dbuser.get_dict().items():
-                    setattr(auth_user, k, v)
+                for k, v in dbuser.get_dict().iteritems():
+                    if k not in ['api_keys', 'permissions']:
+                        setattr(auth_user, k, v)
             else:
                 return False
 
@@ -475,296 +381,6 @@
 
         return True
 
-    def fill_perms(self, user, explicit=True, algo='higherwin'):
-        """
-        Fills user permission attribute with permissions taken from database
-        works for permissions given for repositories, and for permissions that
-        are granted to groups
-
-        :param user: user instance to fill his perms
-        :param explicit: In case there are permissions both for user and a group
-            that user is part of, explicit flag will defiine if user will
-            explicitly override permissions from group, if it's False it will
-            make decision based on the algo
-        :param algo: algorithm to decide what permission should be choose if
-            it's multiple defined, eg user in two different groups. It also
-            decides if explicit flag is turned off how to specify the permission
-            for case when user is in a group + have defined separate permission
-        """
-        RK = 'repositories'
-        GK = 'repositories_groups'
-        UK = 'user_groups'
-        GLOBAL = 'global'
-        user.permissions[RK] = {}
-        user.permissions[GK] = {}
-        user.permissions[UK] = {}
-        user.permissions[GLOBAL] = set()
-
-        def _choose_perm(new_perm, cur_perm):
-            new_perm_val = PERM_WEIGHTS[new_perm]
-            cur_perm_val = PERM_WEIGHTS[cur_perm]
-            if algo == 'higherwin':
-                if new_perm_val > cur_perm_val:
-                    return new_perm
-                return cur_perm
-            elif algo == 'lowerwin':
-                if new_perm_val < cur_perm_val:
-                    return new_perm
-                return cur_perm
-
-        #======================================================================
-        # fetch default permissions
-        #======================================================================
-        default_user = User.get_by_username('default', cache=True)
-        default_user_id = default_user.user_id
-
-        default_repo_perms = Permission.get_default_perms(default_user_id)
-        default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
-        default_user_group_perms = Permission.get_default_user_group_perms(default_user_id)
-
-        if user.is_admin:
-            #==================================================================
-            # admin user have all default rights for repositories
-            # and groups set to admin
-            #==================================================================
-            user.permissions[GLOBAL].add('hg.admin')
-
-            # repositories
-            for perm in default_repo_perms:
-                r_k = perm.UserRepoToPerm.repository.repo_name
-                p = 'repository.admin'
-                user.permissions[RK][r_k] = p
-
-            # repository groups
-            for perm in default_repo_groups_perms:
-                rg_k = perm.UserRepoGroupToPerm.group.group_name
-                p = 'group.admin'
-                user.permissions[GK][rg_k] = p
-
-            # user groups
-            for perm in default_user_group_perms:
-                u_k = perm.UserUserGroupToPerm.user_group.users_group_name
-                p = 'usergroup.admin'
-                user.permissions[UK][u_k] = p
-            return user
-
-        #==================================================================
-        # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS
-        #==================================================================
-        uid = user.user_id
-
-        # default global permissions taken fron the default user
-        default_global_perms = self.sa.query(UserToPerm)\
-            .filter(UserToPerm.user_id == default_user_id)
-
-        for perm in default_global_perms:
-            user.permissions[GLOBAL].add(perm.permission.permission_name)
-
-        # defaults for repositories, taken from default user
-        for perm in default_repo_perms:
-            r_k = perm.UserRepoToPerm.repository.repo_name
-            if perm.Repository.private and not (perm.Repository.user_id == uid):
-                # disable defaults for private repos,
-                p = 'repository.none'
-            elif perm.Repository.user_id == uid:
-                # set admin if owner
-                p = 'repository.admin'
-            else:
-                p = perm.Permission.permission_name
-
-            user.permissions[RK][r_k] = p
-
-        # defaults for repository groups taken from default user permission
-        # on given group
-        for perm in default_repo_groups_perms:
-            rg_k = perm.UserRepoGroupToPerm.group.group_name
-            p = perm.Permission.permission_name
-            user.permissions[GK][rg_k] = p
-
-        # defaults for user groups taken from default user permission
-        # on given user group
-        for perm in default_user_group_perms:
-            u_k = perm.UserUserGroupToPerm.user_group.users_group_name
-            p = perm.Permission.permission_name
-            user.permissions[UK][u_k] = p
-
-        #======================================================================
-        # !! OVERRIDE GLOBALS !! with user permissions if any found
-        #======================================================================
-        # those can be configured from groups or users explicitly
-        _configurable = set([
-            'hg.fork.none', 'hg.fork.repository',
-            'hg.create.none', 'hg.create.repository',
-            'hg.usergroup.create.false', 'hg.usergroup.create.true'
-        ])
-
-        # USER GROUPS comes first
-        # user group global permissions
-        user_perms_from_users_groups = self.sa.query(UserGroupToPerm)\
-            .options(joinedload(UserGroupToPerm.permission))\
-            .join((UserGroupMember, UserGroupToPerm.users_group_id ==
-                   UserGroupMember.users_group_id))\
-            .filter(UserGroupMember.user_id == uid)\
-            .order_by(UserGroupToPerm.users_group_id)\
-            .all()
-        #need to group here by groups since user can be in more than one group
-        _grouped = [[x, list(y)] for x, y in
-                    itertools.groupby(user_perms_from_users_groups,
-                                      lambda x:x.users_group)]
-        for gr, perms in _grouped:
-            # since user can be in multiple groups iterate over them and
-            # select the lowest permissions first (more explicit)
-            ##TODO: do this^^
-            if not gr.inherit_default_permissions:
-                # NEED TO IGNORE all configurable permissions and
-                # replace them with explicitly set
-                user.permissions[GLOBAL] = user.permissions[GLOBAL]\
-                                                .difference(_configurable)
-            for perm in perms:
-                user.permissions[GLOBAL].add(perm.permission.permission_name)
-
-        # user specific global permissions
-        user_perms = self.sa.query(UserToPerm)\
-                .options(joinedload(UserToPerm.permission))\
-                .filter(UserToPerm.user_id == uid).all()
-
-        if not user.inherit_default_permissions:
-            # NEED TO IGNORE all configurable permissions and
-            # replace them with explicitly set
-            user.permissions[GLOBAL] = user.permissions[GLOBAL]\
-                                            .difference(_configurable)
-
-            for perm in user_perms:
-                user.permissions[GLOBAL].add(perm.permission.permission_name)
-        ## END GLOBAL PERMISSIONS
-
-        #======================================================================
-        # !! PERMISSIONS FOR REPOSITORIES !!
-        #======================================================================
-        #======================================================================
-        # check if user is part of user groups for this repository and
-        # fill in his permission from it. _choose_perm decides of which
-        # permission should be selected based on selected method
-        #======================================================================
-
-        # user group for repositories permissions
-        user_repo_perms_from_users_groups = \
-         self.sa.query(UserGroupRepoToPerm, Permission, Repository,)\
-            .join((Repository, UserGroupRepoToPerm.repository_id ==
-                   Repository.repo_id))\
-            .join((Permission, UserGroupRepoToPerm.permission_id ==
-                   Permission.permission_id))\
-            .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
-                   UserGroupMember.users_group_id))\
-            .filter(UserGroupMember.user_id == uid)\
-            .all()
-
-        multiple_counter = collections.defaultdict(int)
-        for perm in user_repo_perms_from_users_groups:
-            r_k = perm.UserGroupRepoToPerm.repository.repo_name
-            multiple_counter[r_k] += 1
-            p = perm.Permission.permission_name
-            cur_perm = user.permissions[RK][r_k]
-
-            if perm.Repository.user_id == uid:
-                # set admin if owner
-                p = 'repository.admin'
-            else:
-                if multiple_counter[r_k] > 1:
-                    p = _choose_perm(p, cur_perm)
-            user.permissions[RK][r_k] = p
-
-        # user explicit permissions for repositories, overrides any specified
-        # by the group permission
-        user_repo_perms = Permission.get_default_perms(uid)
-        for perm in user_repo_perms:
-            r_k = perm.UserRepoToPerm.repository.repo_name
-            cur_perm = user.permissions[RK][r_k]
-            # set admin if owner
-            if perm.Repository.user_id == uid:
-                p = 'repository.admin'
-            else:
-                p = perm.Permission.permission_name
-                if not explicit:
-                    p = _choose_perm(p, cur_perm)
-            user.permissions[RK][r_k] = p
-
-        #======================================================================
-        # !! PERMISSIONS FOR REPOSITORY GROUPS !!
-        #======================================================================
-        #======================================================================
-        # check if user is part of user groups for this repository groups and
-        # fill in his permission from it. _choose_perm decides of which
-        # permission should be selected based on selected method
-        #======================================================================
-        # user group for repo groups permissions
-        user_repo_group_perms_from_users_groups = \
-         self.sa.query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\
-         .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((Permission, UserGroupRepoGroupToPerm.permission_id
-                == Permission.permission_id))\
-         .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
-                == UserGroupMember.users_group_id))\
-         .filter(UserGroupMember.user_id == uid)\
-         .all()
-
-        multiple_counter = collections.defaultdict(int)
-        for perm in user_repo_group_perms_from_users_groups:
-            g_k = perm.UserGroupRepoGroupToPerm.group.group_name
-            multiple_counter[g_k] += 1
-            p = perm.Permission.permission_name
-            cur_perm = user.permissions[GK][g_k]
-            if multiple_counter[g_k] > 1:
-                p = _choose_perm(p, cur_perm)
-            user.permissions[GK][g_k] = p
-
-        # user explicit permissions for repository groups
-        user_repo_groups_perms = Permission.get_default_group_perms(uid)
-        for perm in user_repo_groups_perms:
-            rg_k = perm.UserRepoGroupToPerm.group.group_name
-            p = perm.Permission.permission_name
-            cur_perm = user.permissions[GK][rg_k]
-            if not explicit:
-                p = _choose_perm(p, cur_perm)
-            user.permissions[GK][rg_k] = p
-
-        #======================================================================
-        # !! PERMISSIONS FOR USER GROUPS !!
-        #======================================================================
-        # user group for user group permissions
-        user_group_user_groups_perms = \
-         self.sa.query(UserGroupUserGroupToPerm, Permission, UserGroup)\
-         .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id
-                == UserGroup.users_group_id))\
-         .join((Permission, UserGroupUserGroupToPerm.permission_id
-                == Permission.permission_id))\
-         .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id
-                == UserGroupMember.users_group_id))\
-         .filter(UserGroupMember.user_id == uid)\
-         .all()
-
-        multiple_counter = collections.defaultdict(int)
-        for perm in user_group_user_groups_perms:
-            g_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
-            multiple_counter[g_k] += 1
-            p = perm.Permission.permission_name
-            cur_perm = user.permissions[UK][g_k]
-            if multiple_counter[g_k] > 1:
-                p = _choose_perm(p, cur_perm)
-            user.permissions[UK][g_k] = p
-
-        #user explicit permission for user groups
-        user_user_groups_perms = Permission.get_default_user_group_perms(uid)
-        for perm in user_user_groups_perms:
-            u_k = perm.UserUserGroupToPerm.user_group.users_group_name
-            p = perm.Permission.permission_name
-            cur_perm = user.permissions[UK][u_k]
-            if not explicit:
-                p = _choose_perm(p, cur_perm)
-            user.permissions[UK][u_k] = p
-
-        return user
-
     def has_perm(self, user, perm):
         perm = self._get_perm(perm)
         user = self._get_user(user)
@@ -792,6 +408,7 @@
         new.user = user
         new.permission = perm
         self.sa.add(new)
+        return new
 
     def revoke_perm(self, user, perm):
         """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/model/user_group.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,387 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.model.users_group
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+user group model for RhodeCode
+
+:created_on: Oct 1, 2011
+:author: nvinot, marcink
+"""
+
+
+import logging
+import traceback
+
+from rhodecode.model import BaseModel
+from rhodecode.model.db import UserGroupMember, UserGroup,\
+    UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
+    UserGroupUserGroupToPerm
+from rhodecode.lib.exceptions import UserGroupsAssignedException,\
+    RepoGroupAssignmentError
+
+log = logging.getLogger(__name__)
+
+
+class UserGroupModel(BaseModel):
+
+    cls = UserGroup
+
+    def _get_user_group(self, user_group):
+        return self._get_instance(UserGroup, user_group,
+                                  callback=UserGroup.get_by_group_name)
+
+    def _create_default_perms(self, user_group):
+        # create default permission
+        default_perm = 'usergroup.read'
+        def_user = User.get_default_user()
+        for p in def_user.user_perms:
+            if p.permission.permission_name.startswith('usergroup.'):
+                default_perm = p.permission.permission_name
+                break
+
+        user_group_to_perm = UserUserGroupToPerm()
+        user_group_to_perm.permission = Permission.get_by_key(default_perm)
+
+        user_group_to_perm.user_group = user_group
+        user_group_to_perm.user_id = def_user.user_id
+        return user_group_to_perm
+
+    def _update_permissions(self, user_group, perms_new=None,
+                            perms_updates=None):
+        from rhodecode.lib.auth import HasUserGroupPermissionAny
+        if not perms_new:
+            perms_new = []
+        if not perms_updates:
+            perms_updates = []
+
+        # update permissions
+        for member, perm, member_type in perms_updates:
+            if member_type == 'user':
+                # this updates existing one
+                self.grant_user_permission(
+                    user_group=user_group, user=member, perm=perm
+                )
+            else:
+                #check if we have permissions to alter this usergroup
+                if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write',
+                                             'usergroup.admin')(member):
+                    self.grant_user_group_permission(
+                        target_user_group=user_group, user_group=member, perm=perm
+                    )
+        # set new permissions
+        for member, perm, member_type in perms_new:
+            if member_type == 'user':
+                self.grant_user_permission(
+                    user_group=user_group, user=member, perm=perm
+                )
+            else:
+                #check if we have permissions to alter this usergroup
+                if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write',
+                                             'usergroup.admin')(member):
+                    self.grant_user_group_permission(
+                        target_user_group=user_group, user_group=member, perm=perm
+                    )
+
+    def get(self, user_group_id, cache=False):
+        return UserGroup.get(user_group_id)
+
+    def get_group(self, user_group):
+        return self._get_user_group(user_group)
+
+    def get_by_name(self, name, cache=False, case_insensitive=False):
+        return UserGroup.get_by_group_name(name, cache, case_insensitive)
+
+    def create(self, name, description, owner, active=True, group_data=None):
+        try:
+            new_user_group = UserGroup()
+            new_user_group.user = self._get_user(owner)
+            new_user_group.users_group_name = name
+            new_user_group.user_group_description = description
+            new_user_group.users_group_active = active
+            if group_data:
+                new_user_group.group_data = group_data
+            self.sa.add(new_user_group)
+            perm_obj = self._create_default_perms(new_user_group)
+            self.sa.add(perm_obj)
+
+            self.grant_user_permission(user_group=new_user_group,
+                                       user=owner, perm='usergroup.admin')
+
+            return new_user_group
+        except Exception:
+            log.error(traceback.format_exc())
+            raise
+
+    def update(self, user_group, form_data):
+
+        try:
+            user_group = self._get_user_group(user_group)
+
+            for k, v in form_data.items():
+                if k == 'users_group_members':
+                    user_group.members = []
+                    self.sa.flush()
+                    members_list = []
+                    if v:
+                        v = [v] if isinstance(v, basestring) else v
+                        for u_id in set(v):
+                            member = UserGroupMember(user_group.users_group_id, u_id)
+                            members_list.append(member)
+                    setattr(user_group, 'members', members_list)
+                setattr(user_group, k, v)
+
+            self.sa.add(user_group)
+        except Exception:
+            log.error(traceback.format_exc())
+            raise
+
+    def delete(self, user_group, force=False):
+        """
+        Deletes repository group, unless force flag is used
+        raises exception if there are members in that group, else deletes
+        group and users
+
+        :param user_group:
+        :param force:
+        """
+        user_group = self._get_user_group(user_group)
+        try:
+            # check if this group is not assigned to repo
+            assigned_groups = UserGroupRepoToPerm.query()\
+                .filter(UserGroupRepoToPerm.users_group == user_group).all()
+
+            if assigned_groups and not force:
+                raise UserGroupsAssignedException(
+                    'RepoGroup assigned to %s' % assigned_groups)
+            self.sa.delete(user_group)
+        except Exception:
+            log.error(traceback.format_exc())
+            raise
+
+    def add_user_to_group(self, user_group, user):
+        user_group = self._get_user_group(user_group)
+        user = self._get_user(user)
+
+        for m in user_group.members:
+            u = m.user
+            if u.user_id == user.user_id:
+                # user already in the group, skip
+                return True
+
+        try:
+            user_group_member = UserGroupMember()
+            user_group_member.user = user
+            user_group_member.users_group = user_group
+
+            user_group.members.append(user_group_member)
+            user.group_member.append(user_group_member)
+
+            self.sa.add(user_group_member)
+            return user_group_member
+        except Exception:
+            log.error(traceback.format_exc())
+            raise
+
+    def remove_user_from_group(self, user_group, user):
+        user_group = self._get_user_group(user_group)
+        user = self._get_user(user)
+
+        user_group_member = None
+        for m in user_group.members:
+            if m.user.user_id == user.user_id:
+                # Found this user's membership row
+                user_group_member = m
+                break
+
+        if user_group_member:
+            try:
+                self.sa.delete(user_group_member)
+                return True
+            except Exception:
+                log.error(traceback.format_exc())
+                raise
+        else:
+            # User isn't in that group
+            return False
+
+    def has_perm(self, user_group, perm):
+        user_group = self._get_user_group(user_group)
+        perm = self._get_perm(perm)
+
+        return UserGroupToPerm.query()\
+            .filter(UserGroupToPerm.users_group == user_group)\
+            .filter(UserGroupToPerm.permission == perm).scalar() is not None
+
+    def grant_perm(self, user_group, perm):
+        user_group = self._get_user_group(user_group)
+        perm = self._get_perm(perm)
+
+        # if this permission is already granted skip it
+        _perm = UserGroupToPerm.query()\
+            .filter(UserGroupToPerm.users_group == user_group)\
+            .filter(UserGroupToPerm.permission == perm)\
+            .scalar()
+        if _perm:
+            return
+
+        new = UserGroupToPerm()
+        new.users_group = user_group
+        new.permission = perm
+        self.sa.add(new)
+        return new
+
+    def revokehas_permrevoke_permgrant_perm_perm(self, user_group, perm):
+        user_group = self._get_user_group(user_group)
+        perm = self._get_perm(perm)
+
+        obj = UserGroupToPerm.query()\
+            .filter(UserGroupToPerm.users_group == user_group)\
+            .filter(UserGroupToPerm.permission == perm).scalar()
+        if obj:
+            self.sa.delete(obj)
+
+    def grant_user_permission(self, user_group, user, perm):
+        """
+        Grant permission for user on given user group, or update
+        existing one if found
+
+        :param user_group: Instance of UserGroup, users_group_id,
+            or users_group_name
+        :param user: Instance of User, user_id or username
+        :param perm: Instance of Permission, or permission_name
+        """
+
+        user_group = self._get_user_group(user_group)
+        user = self._get_user(user)
+        permission = self._get_perm(perm)
+
+        # check if we have that permission already
+        obj = self.sa.query(UserUserGroupToPerm)\
+            .filter(UserUserGroupToPerm.user == user)\
+            .filter(UserUserGroupToPerm.user_group == user_group)\
+            .scalar()
+        if obj is None:
+            # create new !
+            obj = UserUserGroupToPerm()
+        obj.user_group = user_group
+        obj.user = user
+        obj.permission = permission
+        self.sa.add(obj)
+        log.debug('Granted perm %s to %s on %s' % (perm, user, user_group))
+        return obj
+
+    def revoke_user_permission(self, user_group, user):
+        """
+        Revoke permission for user on given repository group
+
+        :param user_group: Instance of RepoGroup, repositories_group_id,
+            or repositories_group name
+        :param user: Instance of User, user_id or username
+        """
+
+        user_group = self._get_user_group(user_group)
+        user = self._get_user(user)
+
+        obj = self.sa.query(UserUserGroupToPerm)\
+            .filter(UserUserGroupToPerm.user == user)\
+            .filter(UserUserGroupToPerm.user_group == user_group)\
+            .scalar()
+        if obj:
+            self.sa.delete(obj)
+            log.debug('Revoked perm on %s on %s' % (user_group, user))
+
+    def grant_user_group_permission(self, target_user_group, user_group, perm):
+        """
+        Grant user group permission for given target_user_group
+
+        :param target_user_group:
+        :param user_group:
+        :param perm:
+        """
+        target_user_group = self._get_user_group(target_user_group)
+        user_group = self._get_user_group(user_group)
+        permission = self._get_perm(perm)
+        # forbid assigning same user group to itself
+        if target_user_group == user_group:
+            raise RepoGroupAssignmentError('target repo:%s cannot be '
+                                           'assigned to itself' % target_user_group)
+
+        # check if we have that permission already
+        obj = self.sa.query(UserGroupUserGroupToPerm)\
+            .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
+            .filter(UserGroupUserGroupToPerm.user_group == user_group)\
+            .scalar()
+        if obj is None:
+            # create new !
+            obj = UserGroupUserGroupToPerm()
+        obj.user_group = user_group
+        obj.target_user_group = target_user_group
+        obj.permission = permission
+        self.sa.add(obj)
+        log.debug('Granted perm %s to %s on %s' % (perm, target_user_group, user_group))
+        return obj
+
+    def revoke_user_group_permission(self, target_user_group, user_group):
+        """
+        Revoke user group permission for given target_user_group
+
+        :param target_user_group:
+        :param user_group:
+        """
+        target_user_group = self._get_user_group(target_user_group)
+        user_group = self._get_user_group(user_group)
+
+        obj = self.sa.query(UserGroupUserGroupToPerm)\
+            .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
+            .filter(UserGroupUserGroupToPerm.user_group == user_group)\
+            .scalar()
+        if obj:
+            self.sa.delete(obj)
+            log.debug('Revoked perm on %s on %s' % (target_user_group, user_group))
+
+    def enforce_groups(self, user, groups, extern_type=None):
+        user = self._get_user(user)
+        log.debug('Enforcing groups %s on user %s' % (user, groups))
+        current_groups = user.group_member
+        # find the external created groups
+        externals = [x.users_group for x in current_groups
+                     if 'extern_type' in x.users_group.group_data]
+
+        # calculate from what groups user should be removed
+        # externals that are not in groups
+        for gr in externals:
+            if gr.users_group_name not in groups:
+                log.debug('Removing user %s from user group %s' % (user, gr))
+                self.remove_user_from_group(gr, user)
+
+        # now we calculate in which groups user should be == groups params
+        owner = User.get_first_admin().username
+        for gr in set(groups):
+            existing_group = UserGroup.get_by_group_name(gr)
+            if not existing_group:
+                desc = 'Automatically created from plugin:%s' % extern_type
+                # we use first admin account to set the owner of the group
+                existing_group = UserGroupModel().create(gr, desc, owner,
+                                        group_data={'extern_type': extern_type})
+
+            # we can only add users to special groups created via plugins
+            managed = 'extern_type' in existing_group.group_data
+            if managed:
+                log.debug('Adding user %s to user group %s' % (user, gr))
+                UserGroupModel().add_user_to_group(existing_group, user)
+            else:
+                log.debug('Skipping addition to group %s since it is '
+                          'not managed by auth plugins' % gr)
--- a/rhodecode/model/users_group.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,350 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    rhodecode.model.users_group
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    user group model for RhodeCode
-
-    :created_on: Oct 1, 2011
-    :author: nvinot
-    :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# 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 traceback
-
-from rhodecode.model import BaseModel
-from rhodecode.model.db import UserGroupMember, UserGroup,\
-    UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
-    UserGroupUserGroupToPerm
-from rhodecode.lib.exceptions import UserGroupsAssignedException,\
-    RepoGroupAssignmentError
-
-log = logging.getLogger(__name__)
-
-
-class UserGroupModel(BaseModel):
-
-    cls = UserGroup
-
-    def _get_user_group(self, users_group):
-        return self._get_instance(UserGroup, users_group,
-                                  callback=UserGroup.get_by_group_name)
-
-    def _create_default_perms(self, user_group):
-        # create default permission
-        default_perm = 'usergroup.read'
-        def_user = User.get_default_user()
-        for p in def_user.user_perms:
-            if p.permission.permission_name.startswith('usergroup.'):
-                default_perm = p.permission.permission_name
-                break
-
-        user_group_to_perm = UserUserGroupToPerm()
-        user_group_to_perm.permission = Permission.get_by_key(default_perm)
-
-        user_group_to_perm.user_group = user_group
-        user_group_to_perm.user_id = def_user.user_id
-        return user_group_to_perm
-
-    def _update_permissions(self, user_group, perms_new=None,
-                            perms_updates=None):
-        from rhodecode.lib.auth import HasUserGroupPermissionAny
-        if not perms_new:
-            perms_new = []
-        if not perms_updates:
-            perms_updates = []
-
-        # update permissions
-        for member, perm, member_type in perms_updates:
-            if member_type == 'user':
-                # this updates existing one
-                self.grant_user_permission(
-                    user_group=user_group, user=member, perm=perm
-                )
-            else:
-                #check if we have permissions to alter this usergroup
-                if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write',
-                                             'usergroup.admin')(member):
-                    self.grant_users_group_permission(
-                        target_user_group=user_group, user_group=member, perm=perm
-                    )
-        # set new permissions
-        for member, perm, member_type in perms_new:
-            if member_type == 'user':
-                self.grant_user_permission(
-                    user_group=user_group, user=member, perm=perm
-                )
-            else:
-                #check if we have permissions to alter this usergroup
-                if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write',
-                                             'usergroup.admin')(member):
-                    self.grant_users_group_permission(
-                        target_user_group=user_group, user_group=member, perm=perm
-                    )
-
-    def get(self, users_group_id, cache=False):
-        return UserGroup.get(users_group_id)
-
-    def get_group(self, users_group):
-        return self._get_user_group(users_group)
-
-    def get_by_name(self, name, cache=False, case_insensitive=False):
-        return UserGroup.get_by_group_name(name, cache, case_insensitive)
-
-    def create(self, name, owner, active=True):
-        try:
-            new_user_group = UserGroup()
-            new_user_group.user = self._get_user(owner)
-            new_user_group.users_group_name = name
-            new_user_group.users_group_active = active
-            self.sa.add(new_user_group)
-            perm_obj = self._create_default_perms(new_user_group)
-            self.sa.add(perm_obj)
-
-            self.grant_user_permission(user_group=new_user_group,
-                                       user=owner, perm='usergroup.admin')
-
-            return new_user_group
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
-
-    def update(self, users_group, form_data):
-
-        try:
-            users_group = self._get_user_group(users_group)
-
-            for k, v in form_data.items():
-                if k == 'users_group_members':
-                    users_group.members = []
-                    self.sa.flush()
-                    members_list = []
-                    if v:
-                        v = [v] if isinstance(v, basestring) else v
-                        for u_id in set(v):
-                            member = UserGroupMember(users_group.users_group_id, u_id)
-                            members_list.append(member)
-                    setattr(users_group, 'members', members_list)
-                setattr(users_group, k, v)
-
-            self.sa.add(users_group)
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
-
-    def delete(self, users_group, force=False):
-        """
-        Deletes repository group, unless force flag is used
-        raises exception if there are members in that group, else deletes
-        group and users
-
-        :param users_group:
-        :param force:
-        """
-        try:
-            users_group = self._get_user_group(users_group)
-
-            # check if this group is not assigned to repo
-            assigned_groups = UserGroupRepoToPerm.query()\
-                .filter(UserGroupRepoToPerm.users_group == users_group).all()
-
-            if assigned_groups and not force:
-                raise UserGroupsAssignedException('RepoGroup assigned to %s' %
-                                                   assigned_groups)
-
-            self.sa.delete(users_group)
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
-
-    def add_user_to_group(self, users_group, user):
-        users_group = self._get_user_group(users_group)
-        user = self._get_user(user)
-
-        for m in users_group.members:
-            u = m.user
-            if u.user_id == user.user_id:
-                return True
-
-        try:
-            users_group_member = UserGroupMember()
-            users_group_member.user = user
-            users_group_member.users_group = users_group
-
-            users_group.members.append(users_group_member)
-            user.group_member.append(users_group_member)
-
-            self.sa.add(users_group_member)
-            return users_group_member
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
-
-    def remove_user_from_group(self, users_group, user):
-        users_group = self._get_user_group(users_group)
-        user = self._get_user(user)
-
-        users_group_member = None
-        for m in users_group.members:
-            if m.user.user_id == user.user_id:
-                # Found this user's membership row
-                users_group_member = m
-                break
-
-        if users_group_member:
-            try:
-                self.sa.delete(users_group_member)
-                return True
-            except Exception:
-                log.error(traceback.format_exc())
-                raise
-        else:
-            # User isn't in that group
-            return False
-
-    def has_perm(self, users_group, perm):
-        users_group = self._get_user_group(users_group)
-        perm = self._get_perm(perm)
-
-        return UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group == users_group)\
-            .filter(UserGroupToPerm.permission == perm).scalar() is not None
-
-    def grant_perm(self, users_group, perm):
-        users_group = self._get_user_group(users_group)
-        perm = self._get_perm(perm)
-
-        # if this permission is already granted skip it
-        _perm = UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group == users_group)\
-            .filter(UserGroupToPerm.permission == perm)\
-            .scalar()
-        if _perm:
-            return
-
-        new = UserGroupToPerm()
-        new.users_group = users_group
-        new.permission = perm
-        self.sa.add(new)
-
-    def revoke_perm(self, users_group, perm):
-        users_group = self._get_user_group(users_group)
-        perm = self._get_perm(perm)
-
-        obj = UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group == users_group)\
-            .filter(UserGroupToPerm.permission == perm).scalar()
-        if obj:
-            self.sa.delete(obj)
-
-    def grant_user_permission(self, user_group, user, perm):
-        """
-        Grant permission for user on given user group, or update
-        existing one if found
-
-        :param user_group: Instance of UserGroup, users_group_id,
-            or users_group_name
-        :param user: Instance of User, user_id or username
-        :param perm: Instance of Permission, or permission_name
-        """
-
-        user_group = self._get_user_group(user_group)
-        user = self._get_user(user)
-        permission = self._get_perm(perm)
-
-        # check if we have that permission already
-        obj = self.sa.query(UserUserGroupToPerm)\
-            .filter(UserUserGroupToPerm.user == user)\
-            .filter(UserUserGroupToPerm.user_group == user_group)\
-            .scalar()
-        if obj is None:
-            # create new !
-            obj = UserUserGroupToPerm()
-        obj.user_group = user_group
-        obj.user = user
-        obj.permission = permission
-        self.sa.add(obj)
-        log.debug('Granted perm %s to %s on %s' % (perm, user, user_group))
-
-    def revoke_user_permission(self, user_group, user):
-        """
-        Revoke permission for user on given repository group
-
-        :param user_group: Instance of ReposGroup, repositories_group_id,
-            or repositories_group name
-        :param user: Instance of User, user_id or username
-        """
-
-        user_group = self._get_user_group(user_group)
-        user = self._get_user(user)
-
-        obj = self.sa.query(UserUserGroupToPerm)\
-            .filter(UserUserGroupToPerm.user == user)\
-            .filter(UserUserGroupToPerm.user_group == user_group)\
-            .scalar()
-        if obj:
-            self.sa.delete(obj)
-            log.debug('Revoked perm on %s on %s' % (user_group, user))
-
-    def grant_users_group_permission(self, target_user_group, user_group, perm):
-        """
-        Grant user group permission for given target_user_group
-
-        :param target_user_group:
-        :param user_group:
-        :param perm:
-        """
-        target_user_group = self._get_user_group(target_user_group)
-        user_group = self._get_user_group(user_group)
-        permission = self._get_perm(perm)
-        # forbid assigning same user group to itself
-        if target_user_group == user_group:
-            raise RepoGroupAssignmentError('target repo:%s cannot be '
-                                           'assigned to itself' % target_user_group)
-
-        # check if we have that permission already
-        obj = self.sa.query(UserGroupUserGroupToPerm)\
-            .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
-            .filter(UserGroupUserGroupToPerm.user_group == user_group)\
-            .scalar()
-        if obj is None:
-            # create new !
-            obj = UserGroupUserGroupToPerm()
-        obj.user_group = user_group
-        obj.target_user_group = target_user_group
-        obj.permission = permission
-        self.sa.add(obj)
-        log.debug('Granted perm %s to %s on %s' % (perm, target_user_group, user_group))
-
-    def revoke_users_group_permission(self, target_user_group, user_group):
-        """
-        Revoke user group permission for given target_user_group
-
-        :param target_user_group:
-        :param user_group:
-        """
-        target_user_group = self._get_user_group(target_user_group)
-        user_group = self._get_user_group(user_group)
-
-        obj = self.sa.query(UserGroupUserGroupToPerm)\
-            .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
-            .filter(UserGroupUserGroupToPerm.user_group == user_group)\
-            .scalar()
-        if obj:
-            self.sa.delete(obj)
-            log.debug('Revoked perm on %s on %s' % (target_user_group, user_group))
--- a/rhodecode/model/validators.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/model/validators.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,6 +1,20 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 Set of generic validators
 """
+
 import os
 import re
 import formencode
@@ -16,12 +30,12 @@
 from rhodecode.lib.compat import OrderedSet
 from rhodecode.lib import ipaddr
 from rhodecode.lib.utils import repo_name_slug
-from rhodecode.lib.utils2 import safe_int, str2bool
+from rhodecode.lib.utils2 import safe_int, str2bool, aslist
 from rhodecode.model.db import RepoGroup, Repository, UserGroup, User,\
     ChangesetStatus
 from rhodecode.lib.exceptions import LdapImportError
 from rhodecode.config.routing import ADMIN_PREFIX
-from rhodecode.lib.auth import HasReposGroupPermissionAny, HasPermissionAny
+from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
 
 # silence warnings and pylint
 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
@@ -29,30 +43,10 @@
 
 log = logging.getLogger(__name__)
 
-
-class UniqueList(formencode.FancyValidator):
-    """
-    Unique List !
-    """
-    messages = dict(
-        empty=_('Value cannot be an empty list'),
-        missing_value=_('Value cannot be an empty list'),
-    )
+class _Missing(object):
+    pass
 
-    def _to_python(self, value, state):
-        if isinstance(value, list):
-            return value
-        elif isinstance(value, set):
-            return list(value)
-        elif isinstance(value, tuple):
-            return list(value)
-        elif value is None:
-            return []
-        else:
-            return [value]
-
-    def empty_value(self, value):
-        return []
+Missing = _Missing()
 
 
 class StateObj(object):
@@ -79,6 +73,47 @@
     return self.message(key, state, **kwargs)
 
 
+def UniqueList():
+    class _UniqueList(formencode.FancyValidator):
+        """
+        Unique List !
+        """
+        messages = dict(
+            empty=_('Value cannot be an empty list'),
+            missing_value=_('Value cannot be an empty list'),
+        )
+
+        def _to_python(self, value, state):
+            def make_unique(value):
+                seen = []
+                return [c for c in value if not (c in seen or seen.append(c))]
+
+            if isinstance(value, list):
+                return make_unique(value)
+            elif isinstance(value, set):
+                return make_unique(list(value))
+            elif isinstance(value, tuple):
+                return make_unique(list(value))
+            elif value is None:
+                return []
+            else:
+                return [value]
+
+        def empty_value(self, value):
+            return []
+
+    return _UniqueList
+
+
+def UniqueListFromString():
+    class _UniqueListFromString(UniqueList()):
+        def _to_python(self, value, state):
+            if isinstance(value, basestring):
+                value = aslist(value, ',')
+            return super(_UniqueListFromString, self)._to_python(value, state)
+    return _UniqueListFromString
+
+
 def ValidUsername(edit=False, old_data={}):
     class _validator(formencode.validators.FancyValidator):
         messages = {
@@ -111,6 +146,12 @@
     return _validator
 
 
+def ValidRegex(msg=None):
+    class _validator(formencode.validators.Regex):
+        messages = dict(invalid=msg or _('The input is not valid'))
+    return _validator
+
+
 def ValidRepoUser():
     class _validator(formencode.validators.FancyValidator):
         messages = {
@@ -171,7 +212,7 @@
     return _validator
 
 
-def ValidReposGroup(edit=False, old_data={}):
+def ValidRepoGroup(edit=False, old_data={}):
     class _validator(formencode.validators.FancyValidator):
         messages = {
             'group_parent_id': _(u'Cannot assign this group as parent'),
@@ -247,7 +288,23 @@
     return _validator
 
 
-def ValidPasswordsMatch():
+def ValidOldPassword(username):
+    class _validator(formencode.validators.FancyValidator):
+        messages = {
+            'invalid_password': _(u'Invalid old password')
+        }
+
+        def validate_python(self, value, state):
+            from rhodecode.lib import auth_modules
+            if not auth_modules.authenticate(username, value, ''):
+                msg = M(self, 'invalid_password', state)
+                raise formencode.Invalid(msg, value, state,
+                    error_dict=dict(current_password=msg)
+                )
+    return _validator
+
+
+def ValidPasswordsMatch(passwd='new_password', passwd_confirmation='password_confirmation'):
     class _validator(formencode.validators.FancyValidator):
         messages = {
             'password_mismatch': _(u'Passwords do not match'),
@@ -255,11 +312,11 @@
 
         def validate_python(self, value, state):
 
-            pass_val = value.get('password') or value.get('new_password')
-            if pass_val != value['password_confirmation']:
+            pass_val = value.get('password') or value.get(passwd)
+            if pass_val != value[passwd_confirmation]:
                 msg = M(self, 'password_mismatch', state)
                 raise formencode.Invalid(msg, value, state,
-                     error_dict=dict(password_confirmation=msg)
+                     error_dict={passwd:msg, passwd_confirmation: msg}
                 )
     return _validator
 
@@ -273,12 +330,12 @@
         }
 
         def validate_python(self, value, state):
-            from rhodecode.lib.auth import authenticate
+            from rhodecode.lib import auth_modules
 
             password = value['password']
             username = value['username']
 
-            if not authenticate(username, password):
+            if not auth_modules.authenticate(username, password):
                 user = User.get_by_username(username)
                 if user and not user.active:
                     log.warning('user %s is disabled' % username)
@@ -403,18 +460,16 @@
 def ValidCloneUri():
     from rhodecode.lib.utils import make_ui
 
-    def url_handler(repo_type, url, ui=None):
+    def url_handler(repo_type, url, ui):
         if repo_type == 'hg':
             from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
-            from rhodecode.lib.vcs.utils.hgcompat import httppeer
             if url.startswith('http'):
-                ## initially check if it's at least the proper URL
-                ## or does it pass basic auth
-                MercurialRepository._check_url(url)
-                httppeer(ui, url)._capabilities()
+                # initially check if it's at least the proper URL
+                # or does it pass basic auth
+                MercurialRepository._check_url(url, ui)
             elif url.startswith('svn+http'):
                 from hgsubversion.svnrepo import svnremoterepo
-                svnremoterepo(ui, url).capabilities
+                svnremoterepo(ui, url).svn.uuid
             elif url.startswith('git+http'):
                 raise NotImplementedError()
             else:
@@ -423,8 +478,8 @@
         elif repo_type == 'git':
             from rhodecode.lib.vcs.backends.git.repository import GitRepository
             if url.startswith('http'):
-                ## initially check if it's at least the proper URL
-                ## or does it pass basic auth
+                # initially check if it's at least the proper URL
+                # or does it pass basic auth
                 GitRepository._check_url(url)
             elif url.startswith('svn+http'):
                 raise NotImplementedError()
@@ -491,11 +546,23 @@
         def validate_python(self, value, state):
             gr = RepoGroup.get(value)
             gr_name = gr.group_name if gr else None  # None means ROOT location
-            val = HasReposGroupPermissionAny('group.write', 'group.admin')
+            # create repositories with write permission on group is set to true
+            create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
+            group_admin = HasRepoGroupPermissionAny('group.admin')(gr_name,
+                                            'can write into group validator')
+            group_write = HasRepoGroupPermissionAny('group.write')(gr_name,
+                                            'can write into group validator')
+            forbidden = not (group_admin or (group_write and create_on_write))
             can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository')
-            forbidden = not val(gr_name, 'can write into group validator')
-            value_changed = True  # old_data['repo_group'].get('group_id') != safe_int(value)
-            if value_changed:  # do check if we changed the value
+            gid = (old_data['repo_group'].get('group_id')
+                   if (old_data and 'repo_group' in old_data) else None)
+            value_changed = gid != safe_int(value)
+            new = not old_data
+            # do check if we changed the value, there's a case that someone got
+            # revoked write permissions to a repository, he still created, we
+            # don't need to check permission if he didn't change the value of
+            # groups in form box
+            if value_changed or new:
                 #parent group need to be existing
                 if gr and forbidden:
                     msg = M(self, 'permission_denied', state)
@@ -534,7 +601,7 @@
                 return
 
             forbidden_in_root = gr is None and not can_create_in_root
-            val = HasReposGroupPermissionAny('group.admin')
+            val = HasRepoGroupPermissionAny('group.admin')
             forbidden = not val(gr_name, 'can create group validator')
             if forbidden_in_root or forbidden:
                 msg = M(self, 'permission_denied', state)
@@ -592,7 +659,7 @@
                     t = {'u': 'user',
                          'g': 'users_group'
                     }[k[0]]
-                    if member == 'default':
+                    if member == User.DEFAULT_USER:
                         if str2bool(value.get('repo_private')):
                             # set none for default when updating to
                             # private repo protects agains form manipulation
@@ -718,7 +785,7 @@
 
 
 def AttrLoginValidator():
-    class _validator(formencode.validators.FancyValidator):
+    class _validator(formencode.validators.UnicodeString):
         messages = {
             'invalid_cn':
                   _(u'The LDAP Login attribute of the CN must be specified - '
@@ -825,3 +892,33 @@
                 raise formencode.Invalid(self.message('badPath', state),
                                          value, state)
     return _validator
+
+
+def ValidAuthPlugins():
+    class _validator(formencode.validators.FancyValidator):
+        messages = dict(
+            import_duplicate=_('Plugins %(loaded)s and %(next_to_load)s both export the same name')
+        )
+
+        def _to_python(self, value, state):
+            # filter empty values
+            return filter(lambda s: s not in [None, ''], value)
+
+        def validate_python(self, value, state):
+            from rhodecode.lib import auth_modules
+            module_list = value
+            unique_names = {}
+            try:
+                for module in module_list:
+                    plugin = auth_modules.loadplugin(module)
+                    plugin_name = plugin.name
+                    if plugin_name in unique_names:
+                        msg = M(self, 'import_duplicate', state,
+                                loaded=unique_names[plugin_name],
+                                next_to_load=plugin_name)
+                        raise formencode.Invalid(msg, value, state)
+                    unique_names[plugin_name] = plugin
+            except (ImportError, AttributeError, TypeError), e:
+                raise formencode.Invalid(str(e), value, state)
+
+    return _validator
--- a/rhodecode/templates/admin/admin.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/admin.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,14 +2,17 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Admin journal')} &middot; ${c.rhodecode_name}
+    ${_('Admin journal')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
     <form id="filter_form">
     <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('journal filter...')}"/>
     <span class="tooltip" title="${h.tooltip(h.journal_filter_help())}">?</span>
-    <input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/>
+    <input type='submit' value="${_('filter')}" class="btn btn-mini" style="padding:0px 2px 0px 2px;margin:0px"/>
     ${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.users_log.item_count) % (c.users_log.item_count)}
     </form>
     ${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/auth/auth_settings.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,137 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('Authentication Settings')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${_('Authentication')}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('admin')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+    ${h.form(url('auth_settings'))}
+    <div class="form">
+
+    ## enabled auth plugins
+    <h1>${_('Authentication Plugins')}</h1>
+    <div class="fields">
+       <div class="field">
+           <div class="label"><label for="auth_plugins">${_("Enabled Plugins")}</label></div>
+           <div class="input">${h.text("auth_plugins", class_='large')}
+           <span class="help-block">${_('Comma separated list of plugins. Order of plugins is also order in which RhodeCode will try to authenticate user')}</span>
+               <div style="padding:10px 0px 10px 0px;font-weight: bold">${_('Available built-in plugins')}</div>
+               <ul>
+               %for plugin_path in c.available_plugins:
+                    <li>
+                      <div style="padding:3px 0px 3px 0px">
+                          <span style="margin: 0px 10px 0px 0px" plugin_id="${plugin_path}" class="toggle-plugin btn btn-mini ${'btn-success' if plugin_path in c.enabled_plugins else ''}">
+                          ${_('enabled') if plugin_path in c.enabled_plugins else _('disabled')}</span>${plugin_path}
+                      </div>
+                    </li>
+               %endfor
+               </ul>
+           </div>
+       </div>
+       <div class="buttons">
+          ${h.submit('save',_('Save'),class_="btn")}
+       </div>
+    </div>
+
+    %for cnt, module in enumerate(c.auth_plugins):
+        <% pluginName = c.auth_plugins_shortnames[module] %>
+        <h1>${_('Plugin')}: ${pluginName}</h1>
+        <div class="fields">
+        ## autoform generation, based on plugin definition from it's settings
+        %for setting in c.plugin_settings[module]:
+            <% fullsetting = "auth_%s_%s" % (pluginName, setting["name"]) %>
+            <% displayname = (setting["formname"] if ("formname" in setting) else setting["name"]) %>
+            %if setting["type"] == "password":
+            <div class="field">
+                <div class="label"><label for="${fullsetting}">${_(displayname)}</label></div>
+                <div class="input">
+                    ${h.password(fullsetting,class_='small')}
+                    <span class="help-block">${setting["description"]}</span>
+                </div>
+            </div>
+            %elif setting["type"] in ["string", "int"]:
+            <div class="field">
+                <div class="label"><label for="${fullsetting}">${_(displayname)}</label></div>
+                <div class="input">
+                    ${h.text(fullsetting,class_='small')}
+                    <span class="help-block">${setting["description"]}</span>
+                </div>
+            </div>
+            %elif setting["type"] == "bool":
+            <div class="field">
+                <div class="label label-checkbox"><label for="${fullsetting}">${_(displayname)}</label></div>
+                <div class="checkboxes">
+                    <div class="checkbox">${h.checkbox(fullsetting,True,class_='small')}</div>
+                    <span class="help-block">${setting["description"]}</span>
+                </div>
+            </div>
+            %elif setting["type"] == "select":
+            <div class="field">
+                <div class="label"><label for="${fullsetting}">${_(displayname)}</label></div>
+                <div class="select">
+                    ${h.select(fullsetting,setting['values'][0],setting['values'],class_='small')}
+                    <span class="help-block">${setting["description"]}</span>
+                </div>
+            </div>
+            %else:
+            <div class="field">
+                <div class="label"><label for="${fullsetting}">${_(displayname)}</label></div>
+                <div class="input">This field is of type ${setting['type']}, which cannot be displayed. Must be one of [string|int|bool|select].</div>
+                <span class="help-block">${setting["description"]}</span>
+            </div>
+            %endif
+        %endfor
+        </div>
+    %endfor
+    </div>
+    ${h.end_form()}
+</div>
+
+<script>
+    YUE.on(YUQ('.toggle-plugin'),'click', function(e){
+        var auth_plugins_input = YUD.get('auth_plugins');
+
+        var notEmpty = function(element, index, array) {
+            return (element != "");
+        }
+        var elems = auth_plugins_input.value.split(',').filter(notEmpty);
+        var cur_button = e.currentTarget;
+        var plugin_id = YUD.getAttribute(cur_button, 'plugin_id');
+
+        if(YUD.hasClass(cur_button, 'btn-success')){
+            elems.splice(elems.indexOf(plugin_id), 1);
+            auth_plugins_input.value = elems.join(',');
+            YUD.removeClass(cur_button, 'btn-success');
+            cur_button.innerHTML = _TM['disabled'];
+        }
+        else{
+            console.log(elems)
+            if(elems.indexOf(plugin_id) == -1){
+               elems.push(plugin_id);
+            }
+            auth_plugins_input.value = elems.join(',');
+            YUD.addClass(cur_button, 'btn-success');
+            cur_button.innerHTML = _TM['enabled'];
+        }
+    })
+</script>
+</%def>
--- a/rhodecode/templates/admin/defaults/defaults.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/defaults/defaults.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Repositories defaults')} &middot; ${c.rhodecode_name}
+    ${_('Repositories defaults')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -81,7 +84,7 @@
             </div>
 
             <div class="buttons">
-            ${h.submit('save',_('Save'),class_="ui-btn large")}
+            ${h.submit('save',_('Save'),class_="btn")}
             </div>
         </div>
     </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/gists/edit.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,176 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('Edit Gist')} &middot; ${c.gist.gist_access_id}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="js_extra()">
+<script type="text/javascript" src="${h.url('/js/codemirror.js')}"></script>
+<script type="text/javascript" src="${h.url('/js/codemirror_loadmode.js')}"></script>
+<script type="text/javascript" src="${h.url('/js/mode/meta.js')}"></script>
+<script type="text/javascript" src="${h.url('/js/mode/meta_ext.js')}"></script>
+</%def>
+<%def name="css_extra()">
+<link rel="stylesheet" type="text/css" href="${h.url('/css/codemirror.css')}"/>
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${_('Edit Gist')} &middot; ${c.gist.gist_access_id}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('gists')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+
+    <div class="table">
+        <div id="edit_error" style="display: none" class="flash_msg">
+            <div class="alert alert-dismissable alert-warning">
+              <button type="button" class="close" data-dismiss="alert" aria-hidden="true">ร—</button>
+              ${h.literal(_('Gist was update since you started editing. Copy your changes and click %(here)s to reload new version.')
+                             % {'here': h.link_to('here',h.url('edit_gist', gist_id=c.gist.gist_access_id))})}
+            </div>
+            <script>
+            if (typeof jQuery != 'undefined') {
+                $(".alert").alert();
+            }
+            </script>
+        </div>
+
+        <div id="files_data">
+          ${h.form(h.url('edit_gist', gist_id=c.gist.gist_access_id), method='post', id='eform')}
+            <div>
+                <div class="gravatar">
+                   <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.rhodecode_user.full_contact),32)}"/>
+                </div>
+                <input type="hidden" value="${c.file_changeset.raw_id}" name="parent_hash">
+                <textarea style="resize:vertical; width:400px;border: 1px solid #ccc;border-radius: 3px;"
+                          id="description" name="description"
+                          placeholder="${_('Gist description ...')}">${c.gist.gist_description}</textarea>
+                <div style="padding:0px 0px 0px 42px">
+                    <label for='lifetime'>${_('Gist lifetime')}</label>
+                    ${h.select('lifetime', '0', c.lifetime_options)}
+                    <span class="" style="color: #AAA">
+                     %if c.gist.gist_expires == -1:
+                      ${_('Expires')}: ${_('never')}
+                     %else:
+                      ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))}
+                     %endif
+                   </span>
+                </div>
+            </div>
+
+            % for cnt, file in enumerate(c.files):
+                <div id="body" class="codeblock" style="margin-bottom: 4px">
+                    <div style="padding: 10px 10px 10px 26px;color:#666666">
+                        <input type="hidden" value="${file.path}" name="org_files">
+                        <input id="filename_${h.FID('f',file.path)}" name="files" size="30" type="text" value="${file.path}">
+
+                        <select id="mimetype_${h.FID('f',file.path)}" name="mimetypes">
+                            <option selected="selected" value="plain">${_('plain')}</option>
+                        </select>
+                    </div>
+                    <div class="editor_container">
+                        <pre id="editor_pre"></pre>
+                        <textarea id="editor_${h.FID('f',file.path)}" name="contents" style="display:none">${file.content}</textarea>
+                    </div>
+                </div>
+
+                ## dynamic edit box.
+                <script type="text/javascript">
+                var myCodeMirror = initCodeMirror("editor_${h.FID('f',file.path)}", '');
+                CodeMirror.modeURL = "${h.url('/js/mode/%N/%N.js')}";
+
+                //inject new modes
+                var modes_select = $('#mimetype_${h.FID('f',file.path)}');
+                for(var i=0;i<CodeMirror.modeInfo.length;i++){
+                    var m = CodeMirror.modeInfo[i];
+                    var opt = new Option(m.name, m.mime);
+                    YUD.setAttribute(opt, 'mode', m.mode)
+                    modes_select[0].options[i+1] = opt;
+                }
+
+                var filename_selector = '#filename_${h.FID('f',file.path)}';
+                // on select change set new mode
+                modes_select.on('change', function(e){
+                    var selected = e.currentTarget;
+                    var node = selected.options[selected.selectedIndex];
+                    var mimetype = node.value;
+                    var new_mode = YUD.getAttribute(node, 'mode')
+                    setCodeMirrorMode(myCodeMirror, new_mode);
+
+                    var proposed_ext = getExtFromMimeType(mimetype);
+                    var file_data = getFilenameAndExt($(filename_selector).val());
+                    var filename = file_data['filename'] || 'filename1';
+                    $(filename_selector).val(filename + proposed_ext);
+                })
+
+                // on type the new filename set mode
+                $(filename_selector).on('keyup', function(e){
+                    var file_data = getFilenameAndExt(this.value);
+                    if(file_data['ext'] != null){
+
+                        var mimetype = getMimeTypeFromExt(file_data['ext']);
+                        var detected_mode = detectCodeMirrorMode(this.value, mimetype);
+
+                        if (detected_mode){
+                            setCodeMirrorMode(myCodeMirror, detected_mode);
+                            modes_select.val(mimetype)
+                        }
+                    }
+                })
+
+                // set mode on page load
+                var mimetype = getMimeTypeFromExt("${file.extension}");
+                var detected_mode = detectCodeMirrorMode("${file.path}", mimetype);
+
+                if (detected_mode){
+                    setCodeMirrorMode(myCodeMirror, detected_mode);
+                    modes_select.val(mimetype)
+                }
+
+                </script>
+
+            %endfor
+
+            <div style="padding-top: 5px">
+            ${h.submit('update',_('Update Gist'),class_="btn btn-mini btn-success")}
+            <a class="btn btn-mini" href="${h.url('gist', gist_id=c.gist.gist_access_id)}">${_('Cancel')}</a>
+            </div>
+          ${h.end_form()}
+          <script>
+              $('#update').on('click', function(e){
+                  e.preventDefault();
+
+                  // check for newer version.
+                  $.ajax({
+                    url: "${h.url('edit_gist_check_revision', gist_id=c.gist.gist_access_id)}",
+                    data: {'revision': '${c.file_changeset.raw_id}'},
+                    dataType: 'json',
+                    type: 'POST',
+                    success: function(data) {
+                      if(data.success == false){
+                          $('#edit_error').show();
+                      }
+                      else{
+                        $('#eform').submit();
+                      }
+                    }
+                  })
+              })
+          </script>
+        </div>
+    </div>
+
+</div>
+</%def>
--- a/rhodecode/templates/admin/gists/index.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/gists/index.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,16 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Gists')} &middot; ${c.rhodecode_name}
+    %if c.show_private:
+        ${_('Private Gists for user %s') % c.rhodecode_user.username}
+    %elif c.show_public:
+        ${_('Public Gists for user %s') % c.rhodecode_user.username}
+    %else:
+        ${_('Public Gists')}
+    %endif
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -28,7 +37,7 @@
         %if c.rhodecode_user.username != 'default':
         <ul class="links">
           <li>
-             <span>${h.link_to(_(u'Create new gist'), h.url('new_gist'))}</span>
+             <a href="${h.url('new_gist')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_(u'Create New Gist')}</a>
           </li>
         </ul>
         %endif
@@ -42,7 +51,7 @@
             </div>
             <div title="${gist.owner.full_contact}" class="user" style="font-size: 16px">
                 <b>${h.person(gist.owner.full_contact)}</b> /
-                <b><a href="${h.url('gist',gist_id=gist.gist_access_id)}">gist:${gist.gist_access_id}</a></b>
+                <b><a href="${h.url('gist',gist_id=gist.gist_access_id)}">gist: ${gist.gist_access_id}</a></b>
             </div>
             <div style="padding: 4px 0px 0px 0px">
                 ${_('Created')} ${h.age(gist.created_on)} /
@@ -61,7 +70,7 @@
 
         <div class="notification-paginator">
           <div class="pagination-wh pagination-left">
-          ${c.gists_pager.pager('$link_previous ~2~ $link_next')}
+          ${c.gists_pager.pager('$link_previous ~2~ $link_next', **request.GET.mixed())}
           </div>
         </div>
     %else:
--- a/rhodecode/templates/admin/gists/new.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/gists/new.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('New gist')} &middot; ${c.rhodecode_name}
+    ${_('New Gist')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="js_extra()">
@@ -16,7 +19,7 @@
 </%def>
 
 <%def name="breadcrumbs_links()">
-    ${_('New gist')}
+    ${_('New Gist')}
 </%def>
 
 <%def name="page_nav()">
@@ -45,9 +48,7 @@
             </div>
             <div id="body" class="codeblock">
                 <div style="padding: 10px 10px 10px 26px;color:#666666">
-                    ##<input type="text" value="" size="30" name="filename" id="filename" placeholder="gistfile1.txt">
-                    ${h.text('filename', size=30, placeholder='gistfile1.txt')}
-                    ##<input type="text" value="" size="30" name="filename" id="filename" placeholder="gistfile1.txt">
+                    ${h.text('filename', size=30, placeholder=_('name this file...'))}
                     ${h.select('mimetype','plain',[('plain',_('plain'))])}
                 </div>
                 <div id="editor_container">
@@ -56,9 +57,9 @@
                 </div>
             </div>
             <div style="padding-top: 5px">
-            ${h.submit('private',_('Create private gist'),class_="ui-btn yellow")}
-            ${h.submit('public',_('Create public gist'),class_="ui-btn")}
-            ${h.reset('reset',_('Reset'),class_="ui-btn")}
+            ${h.submit('private',_('Create Private Gist'),class_="btn btn-mini btn-success")}
+            ${h.submit('public',_('Create Public Gist'),class_="btn btn-mini")}
+            ${h.reset('reset',_('Reset'),class_="btn btn-mini")}
             </div>
             ${h.end_form()}
             <script type="text/javascript">
@@ -66,30 +67,40 @@
             CodeMirror.modeURL = "${h.url('/js/mode/%N/%N.js')}";
 
             //inject new modes
-            var modes_select = YUD.get('mimetype');
+            var modes_select = $('#mimetype');
             for(var i=0;i<CodeMirror.modeInfo.length;i++){
                 var m = CodeMirror.modeInfo[i];
                 var opt = new Option(m.name, m.mime);
                 YUD.setAttribute(opt, 'mode', m.mode)
-                modes_select.options[i+1] = opt;
+                modes_select[0].options[i+1] = opt;
             }
-            YUE.on(modes_select, 'change', function(e){
+
+            var filename_selector = '#filename';
+            // on select change set new mode
+            modes_select.on('change', function(e){
                 var selected = e.currentTarget;
                 var node = selected.options[selected.selectedIndex];
                 var mimetype = node.value;
                 var new_mode = YUD.getAttribute(node, 'mode')
                 setCodeMirrorMode(myCodeMirror, new_mode);
 
-                var proposed_mimetypes = MIME_TO_EXT[mimetype] || [];
-                if(proposed_mimetypes.length < 1){
-                    //fallback to text/plain
-                    proposed_mimetypes = ['.txt']
+                var proposed_ext = getExtFromMimeType(mimetype);
+                var file_data = getFilenameAndExt($(filename_selector).val());
+                var filename = file_data['filename'] || 'filename1';
+                $(filename_selector).val(filename + proposed_ext);
+            })
+
+            // on type the new filename set mode
+            $(filename_selector).on('keyup', function(e){
+                var file_data = getFilenameAndExt(this.value);
+                if(file_data['ext'] != null){
+                    var mimetype = getMimeTypeFromExt(file_data['ext']);
+                    var detected_mode = detectCodeMirrorMode(this.value, mimetype);
+                    if (detected_mode){
+                        setCodeMirrorMode(myCodeMirror, detected_mode);
+                        modes_select.val(mimetype)
+                    }
                 }
-                var mt = proposed_mimetypes[0];
-                if(mt[0] == '*'){
-                    mt = mt.substr(1)
-                }
-                YUD.get('filename').value = 'filename1' + mt;
             })
             </script>
         </div>
--- a/rhodecode/templates/admin/gists/show.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/gists/show.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,11 +2,15 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('gist')}:${c.gist.gist_access_id} &middot; ${c.rhodecode_name}
+    ${_('Gist')} &middot; ${c.gist.gist_access_id}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
-    ${_('Gist')} &middot; gist:${c.gist.gist_access_id}
+    ${_('Gist')} &middot; ${c.gist.gist_access_id}
+    / ${_('URL')}: ${c.gist.gist_url()}
 </%def>
 
 <%def name="page_nav()">
@@ -21,7 +25,7 @@
         %if c.rhodecode_user.username != 'default':
         <ul class="links">
           <li>
-             <span>${h.link_to(_(u'Create new gist'), h.url('new_gist'))}</span>
+              <a href="${h.url('new_gist')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_(u'Create New Gist')}</a>
           </li>
         </ul>
         %endif
@@ -33,34 +37,35 @@
                     <div class="stats">
                         <div class="left" style="margin: -4px 0px 0px 0px">
                           %if c.gist.gist_type == 'public':
-                            <div class="ui-btn green badge">${_('Public gist')}</div>
+                            <div class="btn btn-mini btn-success disabled">${_('Public Gist')}</div>
                           %else:
-                            <div class="ui-btn yellow badge">${_('Private gist')}</div>
+                            <div class="btn btn-mini btn-warning disabled">${_('Private Gist')}</div>
                           %endif
                         </div>
-                        <div class="left item ${'' if c.gist.gist_description else 'last'}" style="color: #AAA">
+                        <div class="left item">
+                            ${c.gist.gist_description}
+                        </div>
+                        <div class="left item last" style="color: #AAA">
                          %if c.gist.gist_expires == -1:
                           ${_('Expires')}: ${_('never')}
                          %else:
                           ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))}
                          %endif
                        </div>
-                       <div class="left item last">
-                            ${c.gist.gist_description}
-                       </div>
+
                        %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
-                        <div style="float:right;margin:-4px 0px 0px 0px">
+                        <div style="float:right">
                             ${h.form(url('gist', gist_id=c.gist.gist_id),method='delete')}
-                                ${h.submit('remove_gist', _('Delete'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this gist')+"');")}
+                                ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")}
                             ${h.end_form()}
                         </div>
                        %endif
                         <div class="buttons">
                           ## only owner should see that
-                          ##%if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
-                            ##${h.link_to(_('Edit'),h.url(''),class_="ui-btn")}
-                          ##%endif
-                          ${h.link_to(_('Show as raw'),h.url('formatted_gist', gist_id=c.gist.gist_access_id, format='raw'),class_="ui-btn")}
+                          %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
+                            ${h.link_to(_('Edit'),h.url('edit_gist', gist_id=c.gist.gist_access_id),class_="btn btn-mini")}
+                          %endif
+                          ${h.link_to(_('Show as Raw'),h.url('formatted_gist', gist_id=c.gist.gist_access_id, format='raw'),class_="btn btn-mini")}
                         </div>
                     </div>
 
@@ -80,8 +85,8 @@
                 <div id="${h.FID('G', file.path)}" class="stats" style="border-bottom: 1px solid #DDD;padding: 8px 14px;">
                     <a href="${c.gist.gist_url()}">ยถ</a>
                     <b style="margin:0px 0px 0px 4px">${file.path}</b>
-                    <div style="float:right">
-                       ${h.link_to(_('Show as raw'),h.url('formatted_gist_file', gist_id=c.gist.gist_access_id, format='raw', revision=file.changeset.raw_id, f_path=file.path),class_="ui-btn")}
+                    <div style="float:right; margin: -5px">
+                       ${h.link_to(_('Show as raw'),h.url('formatted_gist_file', gist_id=c.gist.gist_access_id, format='raw', revision=file.changeset.raw_id, f_path=file.path),class_="btn btn-mini")}
                     </div>
                 </div>
                 <div class="code-body">
--- a/rhodecode/templates/admin/ldap/ldap.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%def name="title()">
-    ${_('LDAP administration')} &middot; ${c.rhodecode_name}
-</%def>
-
-<%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))}
-    &raquo;
-    ${_('LDAP')}
-</%def>
-
-<%def name="page_nav()">
-    ${self.menu('admin')}
-</%def>
-
-<%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    ${h.form(url('ldap_settings'))}
-    <div class="form">
-        <div class="fields">
-
-            <h3>${_('Connection settings')}</h3>
-            <div class="field">
-                <div class="label label-checkbox"><label for="ldap_active">${_('Enable LDAP')}</label></div>
-                <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_active',True,class_='small')}</div></div>
-            </div>
-            <div class="field">
-                <div class="label"><label for="ldap_host">${_('Host')}</label></div>
-                <div class="input">${h.text('ldap_host',class_='small')}</div>
-            </div>
-            <div class="field">
-                <div class="label"><label for="ldap_port">${_('Port')}</label></div>
-                <div class="input">${h.text('ldap_port',class_='small')}</div>
-            </div>
-            <div class="field">
-                <div class="label"><label for="ldap_dn_user">${_('Account')}</label></div>
-                <div class="input">${h.text('ldap_dn_user',class_='small')}</div>
-            </div>
-            <div class="field">
-                <div class="label"><label for="ldap_dn_pass">${_('Password')}</label></div>
-                <div class="input">${h.password('ldap_dn_pass',class_='small')}</div>
-            </div>
-            <div class="field">
-                <div class="label"><label for="ldap_tls_kind">${_('Connection security')}</label></div>
-                <div class="select">${h.select('ldap_tls_kind',c.tls_kind_cur,c.tls_kind_choices,class_='small')}</div>
-            </div>
-            <div class="field">
-                <div class="label"><label for="ldap_tls_reqcert">${_('Certificate Checks')}</label></div>
-                <div class="select">${h.select('ldap_tls_reqcert',c.tls_reqcert_cur,c.tls_reqcert_choices,class_='small')}</div>
-            </div>
-            <h3>${_('Search settings')}</h3>
-            <div class="field">
-                <div class="label"><label for="ldap_base_dn">${_('Base DN')}</label></div>
-                <div class="input">${h.text('ldap_base_dn',class_='small')}</div>
-            </div>
-            <div class="field">
-                <div class="label"><label for="ldap_filter">${_('LDAP Filter')}</label></div>
-                <div class="input">${h.text('ldap_filter',class_='small')}</div>
-            </div>
-            <div class="field">
-                <div class="label"><label for="ldap_search_scope">${_('LDAP Search Scope')}</label></div>
-                <div class="select">${h.select('ldap_search_scope',c.search_scope_cur,c.search_scope_choices,class_='small')}</div>
-            </div>
-            <h3>${_('Attribute mappings')}</h3>
-            <div class="field">
-                <div class="label"><label for="ldap_attr_login">${_('Login Attribute')}</label></div>
-                <div class="input">${h.text('ldap_attr_login',class_='small')}</div>
-            </div>
-            <div class="field">
-                <div class="label"><label for="ldap_attr_firstname">${_('First Name Attribute')}</label></div>
-                <div class="input">${h.text('ldap_attr_firstname',class_='small')}</div>
-            </div>
-            <div class="field">
-                <div class="label"><label for="ldap_attr_lastname">${_('Last Name Attribute')}</label></div>
-                <div class="input">${h.text('ldap_attr_lastname',class_='small')}</div>
-            </div>
-            <div class="field">
-                <div class="label"><label for="ldap_attr_email">${_('E-mail Attribute')}</label></div>
-                <div class="input">${h.text('ldap_attr_email',class_='small')}</div>
-            </div>
-
-            <div class="buttons">
-            ${h.submit('save',_('Save'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-</div>
-</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/my_account/my_account.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,54 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('My account')} ${c.rhodecode_user.username}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${_('My Account')}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('admin')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+
+    ##main
+    <div style="width: 150px; float:left">
+        <ul class="nav nav-pills nav-stacked">
+          <li>
+           <div class="gravatar_box" style="height: 26px">
+               <div class="gravatar" style="float: left">
+                   <img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/>
+               </div>
+               <div class="truncate" style="margin:10px 0px 10px 0px; color:#5f5f5f; float:left; width: 100px">
+                <strong>${c.user.username}</strong>
+               </div>
+           </div>
+          </li>
+          <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
+          <li class="${'active' if c.active=='password' else ''}"><a href="${h.url('my_account_password')}">${_('Password')}</a></li>
+          <li class="${'active' if c.active=='api_keys' else ''}"><a href="${h.url('my_account_api_keys')}">${_('API keys')}</a></li>
+          <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('my_account_emails')}">${_('My Emails')}</a></li>
+          <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('My Repositories')}</a></li>
+          <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched')}</a></li>
+          <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.url('my_account_pullrequests')}">${_('Pull requests')}</a></li>
+          <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('My permissions')}</a></li>
+        </ul>
+    </div>
+
+    <div style="min-width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
+        <%include file="/admin/my_account/my_account_${c.active}.html"/>
+    </div>
+</div>
+
+</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/my_account/my_account_api_keys.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,83 @@
+<div class="apikeys_wrap">
+  <table class="noborder">
+    <tr>
+        <td style="width: 450px"><div class="truncate autoexpand" style="width:120px;font-size:16px;font-family: monospace">${c.user.api_key}</div></td>
+        <td>
+            <span class="btn btn-mini btn-success disabled">${_('Built-in')}</span>
+        </td>
+        <td>${_('expires')}: ${_('never')}</td>
+        <td>
+            ${h.form(url('my_account_api_keys'),method='delete')}
+                ${h.hidden('del_api_key',c.user.api_key)}
+                ${h.hidden('del_api_key_builtin',1)}
+                <button class="btn btn-mini btn-danger" type="submit"
+                        onclick="return confirm('${_('Confirm to reset this api key: %s') % c.user.api_key}');">
+                    ${_('reset')}
+                </button>
+            ${h.end_form()}
+        </td>
+    </tr>
+    %if c.user_api_keys:
+        %for api_key in c.user_api_keys:
+          <tr class="${'expired' if api_key.expired else ''}">
+            <td style="width: 450px"><div class="truncate autoexpand" style="width:120px;font-size:16px;font-family: monospace">${api_key.api_key}</div></td>
+            <td>${api_key.description}</td>
+            <td style="min-width: 80px">
+                 %if api_key.expires == -1:
+                  ${_('expires')}: ${_('never')}
+                 %else:
+                    %if api_key.expired:
+                        ${_('expired')}: ${h.age(h.time_to_datetime(api_key.expires))}
+                    %else:
+                        ${_('expires')}: ${h.age(h.time_to_datetime(api_key.expires))}
+                    %endif
+                 %endif
+            </td>
+            <td>
+                ${h.form(url('my_account_api_keys'),method='delete')}
+                    ${h.hidden('del_api_key',api_key.api_key)}
+                    <button class="btn btn-mini btn-danger" type="submit"
+                            onclick="return confirm('${_('Confirm to remove this api key: %s') % api_key.api_key}');">
+                        <i class="icon-remove-sign"></i>
+                        ${_('remove')}
+                    </button>
+                ${h.end_form()}
+            </td>
+          </tr>
+        %endfor
+    %else:
+    <tr><td><div class="ip">${_('No additional api keys specified')}</div></td></tr>
+    %endif
+  </table>
+</div>
+
+<div>
+    ${h.form(url('my_account_api_keys'), method='post')}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+             <div class="field">
+                <div class="label">
+                    <label for="new_email">${_('New api key')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('description', class_='medium', placeholder=_('Description'))}
+                    ${h.select('lifetime', '', c.lifetime_options)}
+                </div>
+             </div>
+            <div class="buttons">
+              ${h.submit('save',_('Add'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
+            </div>
+        </div>
+    </div>
+    ${h.end_form()}
+</div>
+
+<script>
+    $(document).ready(function(){
+        $("#lifetime").select2({
+            'dropdownAutoWidth': true,
+        });
+    })
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/my_account/my_account_emails.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,51 @@
+<div class="emails_wrap">
+  <table class="noborder">
+    <tr>
+    <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email,16)}"/> </div></td>
+    <td><div class="email">${c.user.email}</div></td>
+    <td>
+        <span class="btn btn-mini btn-success disabled">${_('Primary')}</span>
+    </td>
+    </tr>
+    %if c.user_email_map:
+        %for em in c.user_email_map:
+          <tr>
+            <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.email,16)}"/> </div></td>
+            <td><div class="email">${em.email}</div></td>
+            <td>
+                ${h.form(url('my_account_emails'),method='delete')}
+                    ${h.hidden('del_email_id',em.email_id)}
+                    <i class="icon-remove-sign" style="color:#FF4444"></i>
+                    ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
+                    class_="action_button", onclick="return  confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
+                ${h.end_form()}
+            </td>
+          </tr>
+        %endfor
+    %else:
+    <tr><td><div class="ip">${_('No additional emails specified')}</div></td></tr>
+    %endif
+  </table>
+</div>
+
+<div>
+    ${h.form(url('my_account_emails'), method='post')}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+             <div class="field">
+                <div class="label">
+                    <label for="new_email">${_('New email address')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('new_email', class_='medium')}
+                </div>
+             </div>
+            <div class="buttons">
+              ${h.submit('save',_('Add'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
+            </div>
+        </div>
+    </div>
+    ${h.end_form()}
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/my_account/my_account_password.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,38 @@
+<div style="font-size: 20px; color: #666666; padding: 0px 0px 10px 0px">${_('Change your account password')}</div>
+${h.form(url('my_account_password'), method='post')}
+<div class="form">
+    <div class="fields">
+     <div class="field">
+        <div class="label">
+            <label for="current_password">${_('Current password')}:</label>
+        </div>
+        <div class="input">
+            ${h.password('current_password',class_='medium',autocomplete="off")}
+        </div>
+     </div>
+
+     <div class="field">
+        <div class="label">
+            <label for="new_password">${_('New password')}:</label>
+        </div>
+        <div class="input">
+            ${h.password('new_password',class_='medium', autocomplete="off")}
+        </div>
+     </div>
+
+     <div class="field">
+        <div class="label">
+            <label for="password_confirmation">${_('Confirm new password')}:</label>
+        </div>
+        <div class="input">
+            ${h.password('new_password_confirmation',class_='medium', autocomplete="off")}
+        </div>
+     </div>
+
+        <div class="buttons">
+          ${h.submit('save',_('Save'),class_="btn")}
+          ${h.reset('reset',_('Reset'),class_="btn")}
+        </div>
+    </div>
+</div>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/my_account/my_account_perms.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,5 @@
+## permissions overview
+<div id="perms_container">
+<%namespace name="p" file="/base/perms_summary.html"/>
+${p.perms_summary(c.perm_user.permissions, actions=False)}
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/my_account/my_account_profile.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,73 @@
+${h.form(url('my_account'), method='post')}
+    <div class="form">
+
+         <div class="field">
+           <div class="gravatar_box">
+               <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
+                <p>
+                %if c.visual.use_gravatar:
+                <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
+                <br/>${_('Using')} ${c.user.email}
+                %else:
+                <strong>${_('Avatars are disabled')}</strong>
+                <br/>${c.user.email or _('Missing email, please update your user email address.')}
+                    [${_('current IP')}: ${c.perm_user.ip_addr or "?"}]
+                %endif
+               </p>
+           </div>
+         </div>
+
+        <% readonly = None %>
+        <% disabled = "" %>
+        <div class="fields">
+           %if c.extern_type != 'rhodecode':
+                <% readonly = "readonly" %>
+                <% disabled = " disabled" %>
+                <strong>${_('Your user is in an external Source of Record; some details cannot be managed here')}.</strong>
+           %endif
+             <div class="field">
+                <div class="label">
+                    <label for="username">${_('Username')}:</label>
+                </div>
+                <div class="input">
+                  ${h.text('username',class_='medium%s' % disabled, readonly=readonly)}
+                  ${h.hidden('extern_name', c.extern_name)}
+                  ${h.hidden('extern_type', c.extern_type)}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label">
+                    <label for="name">${_('First Name')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('firstname',class_="medium")}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label">
+                    <label for="lastname">${_('Last Name')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('lastname',class_="medium")}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label">
+                    <label for="email">${_('Email')}:</label>
+                </div>
+                <div class="input">
+                    ## we should be able to edit email !
+                    ${h.text('email',class_="medium")}
+                </div>
+             </div>
+
+            <div class="buttons">
+              ${h.submit('save',_('Save'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
+            </div>
+        </div>
+    </div>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/my_account/my_account_pullrequests.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,66 @@
+%if c.show_closed:
+  ${h.checkbox('show_closed',checked="checked", label=_('Show closed pull requests'))}
+%else:
+  ${h.checkbox('show_closed',label=_('Show closed pull requests'))}
+%endif
+<div class="pullrequests_section_head">${_('Opened by me')}</div>
+<ul>
+    %if c.my_pull_requests:
+      %for pull_request in c.my_pull_requests:
+      <li class="${'closed' if pull_request.is_closed() else ''}">
+        <div style="height: 12px">
+          <div style="float:left">
+            <img src="${h.url('/images/icons/flag_status_%s.png' % str(pull_request.last_review_status))}" />
+            <a href="${h.url('pullrequest_show',repo_name=pull_request.other_repo.repo_name,pull_request_id=pull_request.pull_request_id)}">
+              ${_('Pull request #%s opened on %s') % (pull_request.pull_request_id, h.fmt_date(pull_request.created_on))}
+              %if pull_request.is_closed():
+                (${_('Closed')})
+              %endif
+            </a>
+          </div>
+          <div style="float:left;padding:4px 0px 0px 5px">
+            ${h.form(url('pullrequest_delete', repo_name=pull_request.other_repo.repo_name, pull_request_id=pull_request.pull_request_id),method='delete')}
+              <i class="icon-remove-sign" style="color:#FF4444"></i>
+              ${h.submit('remove_%s' % pull_request.pull_request_id, _('delete'),
+              class_="action_button",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
+            ${h.end_form()}
+          </div>
+        </div>
+      </li>
+      %endfor
+   %else:
+    <li><span class="empty_data">${_('Nothing here yet')}</span></li>
+   %endif
+</ul>
+
+<div class="pullrequests_section_head" style="clear:both">${_('I participate in')}</div>
+<ul>
+    %if c.participate_in_pull_requests:
+      %for pull_request in c.participate_in_pull_requests:
+      <li class="${'closed' if pull_request.is_closed() else ''}">
+        <div style="height: 12px">
+          <img src="${h.url('/images/icons/flag_status_%s.png' % str(pull_request.last_review_status))}" />
+          <a href="${h.url('pullrequest_show',repo_name=pull_request.other_repo.repo_name,pull_request_id=pull_request.pull_request_id)}">
+            ${_('Pull request #%s opened by %s on %s') % (pull_request.pull_request_id, pull_request.author.full_name, h.fmt_date(pull_request.created_on))}
+          </a>
+          %if pull_request.is_closed():
+            (${_('Closed')})
+          %endif
+        </div>
+      </li>
+      %endfor
+    %else:
+     <li><span class="empty_data">${_('Nothing here yet')}</span></li>
+    %endif
+</ul>
+
+<script>
+    $('#show_closed').on('click', function(e){
+        if($(this).is(":checked")){
+            window.location = "${h.url('my_account_pullrequests', pr_show_closed=1)}";
+        }
+        else{
+            window.location = "${h.url('my_account_pullrequests')}";
+        }
+    })
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/my_account/my_account_repos.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,107 @@
+<div style="font-size: 20px; color: #666666; padding: 0px 0px 10px 0px">${_('Repositories you are owner of')}</div>
+<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter"
+       placeholder="${_('quick filter...')}" value=""/>
+
+<div class="table-grid table yui-skin-sam" id="repos_list_wrap"></div>
+<div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
+
+<script>
+function table_renderer(data){
+    var myDataSource = new YAHOO.util.DataSource(data);
+    myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
+
+    myDataSource.responseSchema = {
+        resultsList: "records",
+        fields: [
+            {key:"menu"},
+            {key:"raw_name"},
+            {key:"name"},
+            {key:"last_changeset"},
+            {key:"last_rev_raw"},
+            {key:"action"},
+            ]
+        };
+    myDataSource.doBeforeCallback = function(req,raw,res,cb) {
+        // This is the filter function
+        var data     = res.results || [],
+            filtered = [],
+            i,l;
+
+        if (req) {
+            req = req.toLowerCase();
+            for (i = 0; i<data.length; i++) {
+                var pos = data[i].raw_name.toLowerCase().indexOf(req)
+                if (pos != -1) {
+                    filtered.push(data[i]);
+                }
+            }
+            res.results = filtered;
+        }
+        return res;
+    }
+
+      // main table sorting
+      var myColumnDefs = [
+          {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
+          {key:"name",label:"${_('Name')}",sortable:true,
+              sortOptions: { sortFunction: nameSort }},
+          {key:"last_changeset",label:"${_('Tip')}",sortable:true,
+              sortOptions: { sortFunction: revisionSort }},
+          {key:"action",label:"${_('Action')}",sortable:false},
+      ];
+
+      var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
+        sortedBy:{key:"name",dir:"asc"},
+        paginator: YUI_paginator(50, ['user-paginator']),
+
+        MSG_SORTASC:"${_('Click to sort ascending')}",
+        MSG_SORTDESC:"${_('Click to sort descending')}",
+        MSG_EMPTY:"${_('No records found.')}",
+        MSG_ERROR:"${_('Data error.')}",
+        MSG_LOADING:"${_('Loading...')}",
+      }
+      );
+      myDataTable.subscribe('postRenderEvent',function(oArgs) {
+          tooltip_activate();
+          quick_repo_menu();
+      });
+
+      var filterTimeout = null;
+
+      updateFilter = function() {
+          // Reset timeout
+          filterTimeout = null;
+
+          // Reset sort
+          var state = myDataTable.getState();
+          state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
+
+          // Get filtered data
+          myDataSource.sendRequest(YUD.get('q_filter').value,{
+              success : myDataTable.onDataReturnInitializeTable,
+              failure : myDataTable.onDataReturnInitializeTable,
+              scope   : myDataTable,
+              argument: state
+          });
+
+      };
+      YUE.on('q_filter','click',function(){
+          if(!YUD.hasClass('q_filter', 'loaded')){
+              //TODO: load here full list later to do search within groups
+              YUD.addClass('q_filter', 'loaded');
+          }
+       });
+
+      YUE.on('q_filter','keyup',function (e) {
+          clearTimeout(filterTimeout);
+          filterTimeout = setTimeout(updateFilter,600);
+      });
+
+      if(YUD.get('q_filter').value) {
+        updateFilter();
+      }
+
+    }
+
+table_renderer(${c.data |n});
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/my_account/my_account_watched.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,107 @@
+<div style="font-size: 20px; color: #666666; padding: 0px 0px 10px 0px">${_('Repositories you are watching')}</div>
+<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter"
+       placeholder="${_('quick filter...')}" value=""/>
+
+<div class="table-grid table yui-skin-sam" id="repos_list_wrap"></div>
+<div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
+
+<script>
+function table_renderer(data){
+    var myDataSource = new YAHOO.util.DataSource(data);
+    myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
+
+    myDataSource.responseSchema = {
+        resultsList: "records",
+        fields: [
+            {key:"menu"},
+            {key:"raw_name"},
+            {key:"name"},
+            {key:"last_changeset"},
+            {key:"last_rev_raw"},
+            {key:"action"},
+            ]
+        };
+    myDataSource.doBeforeCallback = function(req,raw,res,cb) {
+        // This is the filter function
+        var data     = res.results || [],
+            filtered = [],
+            i,l;
+
+        if (req) {
+            req = req.toLowerCase();
+            for (i = 0; i<data.length; i++) {
+                var pos = data[i].raw_name.toLowerCase().indexOf(req)
+                if (pos != -1) {
+                    filtered.push(data[i]);
+                }
+            }
+            res.results = filtered;
+        }
+        return res;
+    }
+
+      // main table sorting
+      var myColumnDefs = [
+          {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
+          {key:"name",label:"${_('Name')}",sortable:true,
+              sortOptions: { sortFunction: nameSort }},
+          {key:"last_changeset",label:"${_('Tip')}",sortable:true,
+              sortOptions: { sortFunction: revisionSort }},
+          {key:"action",label:"${_('Action')}",sortable:false},
+      ];
+
+      var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
+        sortedBy:{key:"name",dir:"asc"},
+        paginator: YUI_paginator(50, ['user-paginator']),
+
+        MSG_SORTASC:"${_('Click to sort ascending')}",
+        MSG_SORTDESC:"${_('Click to sort descending')}",
+        MSG_EMPTY:"${_('No records found.')}",
+        MSG_ERROR:"${_('Data error.')}",
+        MSG_LOADING:"${_('Loading...')}",
+      }
+      );
+      myDataTable.subscribe('postRenderEvent',function(oArgs) {
+          tooltip_activate();
+          quick_repo_menu();
+      });
+
+      var filterTimeout = null;
+
+      updateFilter = function() {
+          // Reset timeout
+          filterTimeout = null;
+
+          // Reset sort
+          var state = myDataTable.getState();
+          state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
+
+          // Get filtered data
+          myDataSource.sendRequest(YUD.get('q_filter').value,{
+              success : myDataTable.onDataReturnInitializeTable,
+              failure : myDataTable.onDataReturnInitializeTable,
+              scope   : myDataTable,
+              argument: state
+          });
+
+      };
+      YUE.on('q_filter','click',function(){
+          if(!YUD.hasClass('q_filter', 'loaded')){
+              //TODO: load here full list later to do search within groups
+              YUD.addClass('q_filter', 'loaded');
+          }
+       });
+
+      YUE.on('q_filter','keyup',function (e) {
+          clearTimeout(filterTimeout);
+          filterTimeout = setTimeout(updateFilter,600);
+      });
+
+      if(YUD.get('q_filter').value) {
+        updateFilter();
+      }
+
+    }
+
+table_renderer(${c.data |n});
+</script>
--- a/rhodecode/templates/admin/notifications/notifications.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/notifications/notifications.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('My Notifications')} ${c.rhodecode_user.username} &middot; ${c.rhodecode_name}
+    ${_('My Notifications')} ${c.rhodecode_user.username}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -26,13 +29,13 @@
     </div>
 
       <div style="padding:14px 18px;text-align: right;float:left">
-      <span id='all' class="ui-btn"><a href="${h.url.current()}">${_('All')}</a></span>
-      <span id='comment' class="ui-btn"><a href="${h.url.current(type=c.comment_type)}">${_('Comments')}</a></span>
-      <span id='pull_request' class="ui-btn"><a href="${h.url.current(type=c.pull_request_type)}">${_('Pull requests')}</a></span>
+      <span id='all' class="btn btn-mini"><a href="${h.url.current()}">${_('All')}</a></span>
+      <span id='comment' class="btn btn-mini"><a href="${h.url.current(type=c.comment_type)}">${_('Comments')}</a></span>
+      <span id='pull_request' class="btn btn-mini"><a href="${h.url.current(type=c.pull_request_type)}">${_('Pull Requests')}</a></span>
       </div>
       %if c.notifications:
       <div style="padding:14px 18px;text-align: right;float:right">
-      <span id='mark_all_read' class="ui-btn">${_('Mark all read')}</span>
+      <span id='mark_all_read' class="btn btn-mini">${_('Mark all read')}</span>
       </div>
       %endif
   <div id='notification_data'>
--- a/rhodecode/templates/admin/notifications/notifications_data.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/notifications/notifications_data.html	Wed Jul 02 19:03:13 2014 -0400
@@ -14,17 +14,18 @@
       </div>
       <div class="desc ${unread(notification.read)}">
       <a href="${url('notification', notification_id=notification.notification.notification_id)}">${notification.notification.description}</a>
+
       </div>
       <div class="delete-notifications">
-        <span id="${notification.notification.notification_id}" class="delete-notification delete_icon action"></span>
+        <span id="${notification.notification.notification_id}" class="delete-notification"><i class="icon-minus-sign" id="yui-gen24" style="color: #b94a48"></i></span>
       </div>
       %if not notification.read:
       <div class="read-notifications">
-        <span id="${notification.notification.notification_id}" class="read-notification accept_icon action"></span>
+        <span id="${notification.notification.notification_id}" class="read-notification"><i class="icon-ok-sign" id="yui-gen24" style="color: #4CBB17"></i></span>
       </div>
       %endif
     </div>
-    <div class="notification-subject">${h.literal(notification.notification.subject)}</div>
+        <div class="notification-subject"></div>
   </div>
 %endfor
 </div>
--- a/rhodecode/templates/admin/notifications/show_notification.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/notifications/show_notification.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Show notification')} ${c.rhodecode_user.username} &middot; ${c.rhodecode_name}
+    ${_('Show notification')} ${c.rhodecode_user.username}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -36,7 +39,7 @@
               ${c.notification.description}
           </div>
           <div class="delete-notifications">
-            <span id="${c.notification.notification_id}" class="delete-notification delete_icon action"></span>
+            <span id="${c.notification.notification_id}" class="delete-notification action"><i class="icon-minus-sign" id="yui-gen24"></i></span>
           </div>
         </div>
         <div class="notification-body">
--- a/rhodecode/templates/admin/permissions/permissions.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/permissions/permissions.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Permissions administration')} &middot; ${c.rhodecode_name}
+    ${_('Permissions administration')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -15,177 +18,35 @@
     ${self.menu('admin')}
 </%def>
 
+
 <%def name="main()">
-<div class="box box-left">
-    <!-- box / title -->
+<div class="box">
     <div class="title">
         ${self.breadcrumbs()}
     </div>
-    <h3>${_('Default permissions')}</h3>
-    ${h.form(url('permission', id='default'),method='put')}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="anonymous">${_('Anonymous access')}:</label>
-                </div>
-                <div class="checkboxes">
-                    <div class="checkbox">
-                        ${h.checkbox('anonymous',True)}
-                    </div>
-                </div>
-            </div>
-            <div class="field">
-                <div class="label">
-                    <label for="default_repo_perm">${_('Repository')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_repo_perm','',c.repo_perms_choices)}
-
-                    ${h.checkbox('overwrite_default_repo','true')}
-                    <label for="overwrite_default_repo">
-                    <span class="tooltip"
-                    title="${h.tooltip(_('All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost'))}">
-                    ${_('Overwrite existing settings')}</span> </label>
-                </div>
-            </div>
-            <div class="field">
-                <div class="label">
-                    <label for="default_group_perm">${_('Repository group')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_group_perm','',c.group_perms_choices)}
-                    ${h.checkbox('overwrite_default_group','true')}
-                    <label for="overwrite_default_group">
-                    <span class="tooltip"
-                    title="${h.tooltip(_('All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost'))}">
-                    ${_('Overwrite existing settings')}</span> </label>
 
-                </div>
-            </div>
-            <div class="field">
-                <div class="label">
-                    <label for="default_group_perm">${_('User group')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_user_group_perm','',c.user_group_perms_choices)}
-                    ${h.checkbox('overwrite_default_user_group','true')}
-                    <label for="overwrite_default_user_group">
-                    <span class="tooltip"
-                    title="${h.tooltip(_('All default permissions on each user group will be reset to chosen permission, note that all custom default permission on repository groups will be lost'))}">
-                    ${_('Overwrite existing settings')}</span> </label>
+    ##main
+    <div style="width: 150px; float:left">
+        <ul class="nav nav-pills nav-stacked">
+          <li>
+           <div class="gravatar_box" style="height: 26px">
+               <div class="gravatar" style="float: left">
+                <i class="icon-ban-circle" style="font-size: 26px"></i>
+               </div>
+               <div class="truncate" style="margin:10px 0px 10px 0px; color:#5f5f5f; float:left; width: 100px">
+                <strong>${_('Permissions')}</strong>
+               </div>
+           </div>
+          </li>
+          <li class="${'active' if c.active=='globals' else ''}"><a href="${h.url('admin_permissions')}">${_('Global')}</a></li>
+          <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('admin_permissions_ips')}">${_('IP whitelist')}</a></li>
+          <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('admin_permissions_perms')}">${_('Overview')}</a></li>
+        </ul>
+    </div>
 
-                </div>
-            </div>
-             <div class="field">
-                <div class="label">
-                    <label for="default_repo_create">${_('Repository creation')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_repo_create','',c.repo_create_choices)}
-                </div>
-             </div>
-             <div class="field">
-                <div class="label">
-                    <label for="default_user_group_create">${_('User group creation')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_user_group_create','',c.user_group_create_choices)}
-                </div>
-             </div>
-             <div class="field">
-                <div class="label">
-                    <label for="default_fork">${_('Repository forking')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_fork','',c.fork_choices)}
-                </div>
-             </div>
-             <div class="field">
-                <div class="label">
-                    <label for="default_register">${_('Registration')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_register','',c.register_choices)}
-                </div>
-             </div>
-             <div class="field">
-                <div class="label">
-                    <label for="default_extern_activate">${_('External auth account activation')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_extern_activate','',c.extern_activate_choices)}
-                </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-            </div>
-        </div>
+    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
+        <%include file="/admin/permissions/permissions_${c.active}.html"/>
     </div>
-    ${h.end_form()}
 </div>
 
-<div style="min-height:780px" class="box box-right">
-    <!-- box / title -->
-    <div class="title">
-        <h5>${_('Default User Permissions')}</h5>
-    </div>
-
-    ## permissions overview
-    <%namespace name="p" file="/base/perms_summary.html"/>
-    ${p.perms_summary(c.perm_user.permissions, show_all=True)}
-
-</div>
-<div class="box box-left" style="clear:left">
-    <!-- box / title -->
-    <div class="title">
-        <h5>${_('Allowed IP addresses')}</h5>
-    </div>
-
-    <div class="ips_wrap">
-      <table class="noborder">
-      %if c.user_ip_map:
-        %for ip in c.user_ip_map:
-          <tr>
-              <td><div class="ip">${ip.ip_addr}</div></td>
-              <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
-              <td>
-                ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
-                    ${h.hidden('del_ip',ip.ip_id)}
-                    ${h.hidden('default_user', 'True')}
-                    ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
-                    class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
-                ${h.end_form()}
-              </td>
-          </tr>
-        %endfor
-       %else:
-        <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
-       %endif
-      </table>
-    </div>
-
-    ${h.form(url('user_ips', id=c.user.user_id),method='put')}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="new_ip">${_('New ip address')}:</label>
-                </div>
-                <div class="input">
-                    ${h.hidden('default_user', 'True')}
-                    ${h.text('new_ip', class_='medium')}
-                </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Add'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-</div>
 </%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/permissions/permissions_globals.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,113 @@
+${h.form(url('admin_permissions'), method='post')}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="anonymous">${_('Anonymous access')}:</label>
+                </div>
+                <div class="checkboxes">
+                    <div class="checkbox">
+                        ${h.checkbox('anonymous',True)}
+                    </div>
+                     <span class="help-block">${h.literal(_('Allow access to RhodeCode without need to log in. Anonymous users use %s user permissions' % (h.link_to('*default*',h.url('admin_permissions_perms')))))}</span>
+                </div>
+            </div>
+            <div class="field">
+                <div class="label">
+                    <label for="default_repo_perm">${_('Repository')}:</label>
+                </div>
+                <div class="select">
+                    ${h.select('default_repo_perm','',c.repo_perms_choices)}
+
+                    ${h.checkbox('overwrite_default_repo','true')}
+                    <label for="overwrite_default_repo">
+                    <span class="tooltip"
+                    title="${h.tooltip(_('All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost'))}">
+                    ${_('Overwrite existing settings')}</span> </label>
+                </div>
+            </div>
+            <div class="field">
+                <div class="label">
+                    <label for="default_group_perm">${_('Repository group')}:</label>
+                </div>
+                <div class="select">
+                    ${h.select('default_group_perm','',c.group_perms_choices)}
+                    ${h.checkbox('overwrite_default_group','true')}
+                    <label for="overwrite_default_group">
+                    <span class="tooltip"
+                    title="${h.tooltip(_('All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost'))}">
+                    ${_('Overwrite existing settings')}</span> </label>
+
+                </div>
+            </div>
+            <div class="field">
+                <div class="label">
+                    <label for="default_group_perm">${_('User group')}:</label>
+                </div>
+                <div class="select">
+                    ${h.select('default_user_group_perm','',c.user_group_perms_choices)}
+                    ${h.checkbox('overwrite_default_user_group','true')}
+                    <label for="overwrite_default_user_group">
+                    <span class="tooltip"
+                    title="${h.tooltip(_('All default permissions on each user group will be reset to chosen permission, note that all custom default permission on repository groups will be lost'))}">
+                    ${_('Overwrite existing settings')}</span> </label>
+
+                </div>
+            </div>
+             <div class="field">
+                <div class="label">
+                    <label for="default_repo_create">${_('Repository creation')}:</label>
+                </div>
+                <div class="select">
+                    ${h.select('default_repo_create','',c.repo_create_choices)}
+                </div>
+             </div>
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="create_on_write">${_('Repository creation with group write access')}:</label>
+                </div>
+                <div class="select">
+                    ${h.select('create_on_write','',c.repo_create_on_write_choices)}
+                    <span class="help-block">${_('Write permission to repository groups allows creating repositories inside that group')}</span>
+                </div>
+            </div>
+             <div class="field">
+                <div class="label">
+                    <label for="default_user_group_create">${_('User group creation')}:</label>
+                </div>
+                <div class="select">
+                    ${h.select('default_user_group_create','',c.user_group_create_choices)}
+                </div>
+             </div>
+             <div class="field">
+                <div class="label">
+                    <label for="default_fork">${_('Repository forking')}:</label>
+                </div>
+                <div class="select">
+                    ${h.select('default_fork','',c.fork_choices)}
+                </div>
+             </div>
+             <div class="field">
+                <div class="label">
+                    <label for="default_register">${_('Registration')}:</label>
+                </div>
+                <div class="select">
+                    ${h.select('default_register','',c.register_choices)}
+                </div>
+             </div>
+             <div class="field">
+                <div class="label">
+                    <label for="default_extern_activate">${_('External auth account activation')}:</label>
+                </div>
+                <div class="select">
+                    ${h.select('default_extern_activate','',c.extern_activate_choices)}
+                </div>
+             </div>
+            <div class="buttons">
+              ${h.submit('save',_('Save'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
+            </div>
+        </div>
+    </div>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/permissions/permissions_ips.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,45 @@
+<h4>${_('Default ip whitelist for all users')}</h4>
+
+<div class="ips_wrap">
+      <table class="noborder">
+      %if c.user_ip_map:
+        %for ip in c.user_ip_map:
+          <tr>
+              <td><div class="ip">${ip.ip_addr}</div></td>
+              <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
+              <td>
+                ${h.form(url('edit_user_ips', id=c.user.user_id),method='delete')}
+                    ${h.hidden('del_ip_id',ip.ip_id)}
+                    ${h.hidden('default_user', 'True')}
+                    <i class="icon-remove-sign" style="color:#FF4444"></i> ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
+                    class_="action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
+                ${h.end_form()}
+              </td>
+          </tr>
+        %endfor
+       %else:
+        <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
+       %endif
+      </table>
+</div>
+
+${h.form(url('edit_user_ips', id=c.user.user_id),method='put')}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+             <div class="field">
+                <div class="label">
+                    <label for="new_ip">${_('New ip address')}:</label>
+                </div>
+                <div class="input">
+                    ${h.hidden('default_user', 'True')}
+                    ${h.text('new_ip', class_='medium')}
+                </div>
+             </div>
+            <div class="buttons">
+              ${h.submit('save',_('Add'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
+            </div>
+        </div>
+    </div>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/permissions/permissions_perms.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,5 @@
+<h4>${_('Default user permissions overview')}</h4>
+
+## permissions overview
+<%namespace name="p" file="/base/perms_summary.html"/>
+${p.perms_summary(c.perm_user.permissions, show_all=True)}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repo_groups/repo_group_add.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,98 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('Add repository group')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${h.link_to(_('Repository groups'),h.url('repos_groups'))}
+    &raquo;
+    ${_('Add Repository Group')}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('admin')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+    <!-- end box / title -->
+    ${h.form(url('repos_groups'))}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+             <div class="field">
+                <div class="label">
+                    <label for="group_name">${_('Group name')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('group_name',class_='small')}
+                </div>
+             </div>
+
+            <div class="field">
+                <div class="label label-textarea">
+                    <label for="group_description">${_('Description')}:</label>
+                </div>
+                <div class="textarea-repo editor">
+                    ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
+                </div>
+            </div>
+
+            <div class="field">
+                 <div class="label">
+                     <label for="group_parent_id">${_('Group parent')}:</label>
+                 </div>
+                 <div class="input">
+                     ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
+                 </div>
+            </div>
+
+            <div id="copy_perms" class="field">
+                <div class="label label-checkbox">
+                    <label for="group_copy_permissions">${_('Copy parent group permissions')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('group_copy_permissions',value="True")}
+                    <span class="help-block">${_('Copy permission set from parent repository group.')}</span>
+                </div>
+            </div>
+
+            <div class="buttons">
+              ${h.submit('save',_('Save'),class_="btn")}
+            </div>
+        </div>
+    </div>
+    ${h.end_form()}
+</div>
+<script>
+    $(document).ready(function(){
+        var setCopyPermsOption = function(group_val){
+            if(group_val != "-1"){
+                $('#copy_perms').show()
+            }
+            else{
+                $('#copy_perms').hide();
+            }
+        }
+        $("#group_parent_id").select2({
+            'dropdownAutoWidth': true
+        });
+        setCopyPermsOption($('#group_parent_id').val())
+        $("#group_parent_id").on("change", function(e) {
+            setCopyPermsOption(e.val)
+        })
+        $('#group_name').focus();
+    })
+</script>
+</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repo_groups/repo_group_edit.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,63 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('%s Repository group settings') % c.repo_group.name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${h.link_to(_('Repository Groups'),h.url('repos_groups'))}
+    %if c.repo_group.parent_group:
+        &raquo; ${h.link_to(c.repo_group.parent_group.name,h.url('repos_group_home',group_name=c.repo_group.parent_group.group_name))}
+    %endif
+    &raquo; ${c.repo_group.name}
+</%def>
+
+<%def name="breadcrumbs_side_links()">
+    <ul class="links">
+      <li>
+          <a href="${h.url('new_repos_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_(u'Add Child Group')}</a>
+      </li>
+    </ul>
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('admin')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <div class="title">
+        ${self.breadcrumbs()}
+        ${self.breadcrumbs_side_links()}
+    </div>
+
+    ##main
+    <div style="width: 150px; float:left">
+        <ul class="nav nav-pills nav-stacked">
+          <li>
+           <div class="gravatar_box" style="height: 26px">
+             <div class="gravatar" style="float: left">
+                <i class="icon-folder-close" style="font-size: 26px"></i>
+             </div>
+               <div class="truncate" style="margin:10px 0px 10px 0px; color:#5f5f5f; float:left; width: 100px">
+                <strong>${c.repo_group.name}</strong>
+               </div>
+           </div>
+          </li>
+          <li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_repo_group', group_name=c.repo_group.group_name)}">${_('Settings')}</a></li>
+          <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_repo_group_advanced', group_name=c.repo_group.group_name)}">${_('Advanced')}</a></li>
+          <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_repo_group_perms', group_name=c.repo_group.group_name)}">${_('Permissions')}</a></li>
+        </ul>
+    </div>
+
+    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
+        <%include file="/admin/repo_groups/repo_group_edit_${c.active}.html"/>
+    </div>
+</div>
+</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,27 @@
+<div style="font-size: 24px; color: #666666; padding: 0px 0px 10px 0px">${_('Repository Group: %s') % c.repo_group.group_name}</div>
+
+<dl class="dl-horizontal">
+<%
+ elems = [
+    (_('Top level repositories'), c.repo_group.repositories.count(), ''),
+    (_('Total repositories'), c.repo_group.repositories_recursive_count, ''),
+    (_('Children groups'), c.repo_group.children.count(), ''),
+    (_('Created on'), h.fmt_date(c.repo_group.created_on), ''),
+    (_('Owner'), h.person(c.repo_group.user.username), ''),
+ ]
+%>
+%for dt, dd, tt in elems:
+  <dt style="width:150px; text-align: left">${dt}:</dt>
+  <dd style="margin-left: 160px" title="${tt}">${dd}</dd>
+%endfor
+</dl>
+
+${h.form(h.url('repos_group', group_name=c.repo_group.group_name),method='delete')}
+    <button class="btn btn-small btn-danger" type="submit"
+            onclick="return confirm('${ungettext('Confirm to delete this group: %s with %s repository',
+          'Confirm to delete this group: %s with %s repositories',
+ c.repo_group.repositories_recursive_count) % (c.repo_group.group_name, c.repo_group.repositories_recursive_count)}');">
+        <i class="icon-remove-sign"></i>
+        ${_('Delete this repository group')}
+    </button>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,139 @@
+${h.form(url('edit_repo_group_perms', group_name=c.repo_group.group_name),method='put')}
+<div class="form">
+   <div class="fields">
+        <div class="field">
+            <table id="permissions_manage" class="noborder">
+                <tr>
+                    <td>${_('none')}</td>
+                    <td>${_('read')}</td>
+                    <td>${_('write')}</td>
+                    <td>${_('admin')}</td>
+                    <td>${_('user/user group')}</td>
+                    <td></td>
+                </tr>
+                ## USERS
+                %for r2p in c.repo_group.repo_group_to_perm:
+                    ##forbid revoking permission from yourself, except if you're an super admin
+                    <tr id="id${id(r2p.user.username)}">
+                        %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin:
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
+                        <td style="white-space: nowrap;">
+                            <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>
+                            %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default':
+                             <a href="${h.url('edit_user',id=r2p.user.user_id)}">${r2p.user.username}</a>
+                            %else:
+                             ${r2p.user.username if r2p.user.username != 'default' else _('default')}
+                            %endif
+                        </td>
+                        <td>
+                          %if r2p.user.username !='default':
+                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
+                             <i class="icon-remove"></i> ${_('revoke')}
+                            </span>
+                          %endif
+                        </td>
+                        %else:
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none', disabled="disabled")}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read', disabled="disabled")}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write', disabled="disabled")}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")}</td>
+                        <td style="white-space: nowrap;">
+                            <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>
+                            ${r2p.user.username if r2p.user.username != 'default' else _('default')}
+                        </td>
+                        <td><i class="icon-user"></i> ${_('delegated admin')}</td>
+                        %endif
+                    </tr>
+                %endfor
+
+                ## USER GROUPS
+                %for g2p in c.repo_group.users_group_to_perm:
+                    <tr id="id${id(g2p.users_group.users_group_name)}">
+                        <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
+                        <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
+                        <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
+                        <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
+                        <td style="white-space: nowrap;">
+                            <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
+                            %if h.HasPermissionAny('hg.admin')():
+                             <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">
+                                 ${g2p.users_group.users_group_name}
+                             </a>
+                            %else:
+                             ${g2p.users_group.users_group_name}
+                            %endif
+                        </td>
+                        <td>
+                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}', '${g2p.users_group.users_group_name}')">
+                            <i class="icon-remove"></i> ${_('revoke')}
+                            </span>
+                        </td>
+                    </tr>
+                %endfor
+
+                <%
+                _tmpl = h.literal("""' \
+                    <td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td><input type="radio" value="group.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td><input type="radio" value="group.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td class="ac"> \
+                        <div class="perm_ac" id="perm_ac_{0}"> \
+                            <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
+                            <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">  \
+                            <div id="perm_container_{0}"></div> \
+                        </div> \
+                    </td> \
+                    <td></td>'""")
+                %>
+                ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
+                <tr class="new_members last_new_member" id="add_perm_input"></tr>
+                <tr>
+                    <td colspan="6">
+                        <span id="add_perm" style="cursor: pointer;">
+                            <i class="icon-plus"></i> ${_('Add new')}
+                        </span>
+                    </td>
+                </tr>
+                <tr>
+                    <td colspan="6">
+                       ${_('apply to children')}:
+                       ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
+                       ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
+                       ${h.radio('recursive', 'repos', label=_('Repositories'))}
+                       ${h.radio('recursive', 'all', label=_('Both'))}
+                       <span class="help-block">${_('Set or revoke permission to all children of that group, including non-private repositories and other groups if selected.')}</span>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div class="buttons">
+          ${h.submit('save',_('Save'),class_="btn")}
+          ${h.reset('reset',_('Reset'),class_="btn")}
+        </div>
+   </div>
+</div>
+${h.end_form()}
+
+<script type="text/javascript">
+    function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
+        url = "${h.url('edit_repo_group_perms', group_name=c.repo_group.group_name)}";
+        var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
+        if (confirm(revoke_msg)){
+            var recursive = $('input[name=recursive]:checked').val();
+            ajaxActionRevokePermission(url, obj_id, obj_type, field_id, {recursive:recursive});
+        }
+    };
+
+    YUE.onDOMReady(function () {
+        if (!YUD.hasClass('perm_new_member_name', 'error')) {
+            YUD.setStyle('add_perm_input', 'display', 'none');
+        }
+        YAHOO.util.Event.addListener('add_perm', 'click', function () {
+            addPermAction(${_tmpl}, ${c.users_array|n}, ${c.user_groups_array|n});
+        });
+    });
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,55 @@
+## -*- coding: utf-8 -*-
+${h.form(url('repos_group',group_name=c.repo_group.group_name),method='put')}
+<div class="form">
+    <!-- fields -->
+    <div class="fields">
+        <div class="field">
+            <div class="label">
+                <label for="group_name">${_('Group name')}:</label>
+            </div>
+            <div class="input">
+                ${h.text('group_name',class_='medium')}
+            </div>
+        </div>
+
+        <div class="field">
+            <div class="label label-textarea">
+                <label for="group_description">${_('Description')}:</label>
+            </div>
+            <div class="textarea text-area editor">
+                ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
+            </div>
+        </div>
+
+        <div class="field">
+            <div class="label">
+                <label for="group_parent_id">${_('Group parent')}:</label>
+            </div>
+            <div class="input">
+                ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
+            </div>
+        </div>
+        <div class="field">
+            <div class="label label-checkbox">
+                <label for="enable_locking">${_('Enable locking')}:</label>
+            </div>
+            <div class="checkboxes">
+                ${h.checkbox('enable_locking',value="True")}
+                <span class="help-block">${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')}</span>
+            </div>
+        </div>
+        <div class="buttons">
+          ${h.submit('save',_('Save'),class_="btn")}
+          ${h.reset('reset',_('Reset'),class_="btn")}
+        </div>
+    </div>
+</div>
+${h.end_form()}
+
+<script>
+    $(document).ready(function(){
+        $("#group_parent_id").select2({
+            'dropdownAutoWidth': true
+        });
+    })
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repo_groups/repo_group_show.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,26 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+<%def name="title()">
+    ${_('%s Repository group dashboard') % c.group.group_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="breadcrumbs()">
+    <span class="groups_breadcrumbs">
+    ${h.link_to(_(u'Home'),h.url('/'))}
+    %if c.group.parent_group:
+        &raquo; ${h.link_to(c.group.parent_group.name,h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
+    %endif
+    &raquo; "${c.group.name}" ${_('with')}
+    </span>
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('repositories')}
+</%def>
+
+<%def name="main()">
+        <%include file="/index_base.html" args="parent=self,group_name=c.group.group_name"/>
+</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repo_groups/repo_groups.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,60 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('Repository groups administration')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="breadcrumbs_links()">
+    <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
+    ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
+</%def>
+
+
+<%def name="page_nav()">
+    ${self.menu('admin')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+        <ul class="links">
+            %if h.HasPermissionAny('hg.admin')():
+             <li>
+               <a href="${h.url('new_repos_group')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_(u'Add Repository Group')}</a>
+             </li>
+            %endif
+        </ul>
+    </div>
+    <!-- end box / title -->
+    <div class="table-grid table yui-skin-sam" id="datatable_list_wrap"></div>
+    <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
+</div>
+<script>
+  var data = ${c.data|n};
+  var fields = [
+    {key: "group_name"},
+    {key: "raw_name"},
+    {key: "desc"},
+    {key: "repos"},
+    {key: "owner"},
+    {key: "action"},
+
+  ];
+  var column_defs = [
+    {key:"group_name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: nameSort }},
+    {key:"desc",label:"${_('Description')}",sortable:true},
+    {key:"repos",label:"${_('Number of toplevel repositories')}",sortable:true},
+    {key:"owner",label:"${_('Owner')}",sortable:true},
+    {key:"action",label:"${_('Action')}",sortable:false},
+  ];
+  var counter = YUD.get('repo_group_count');
+  var sort_key = "group_name";
+  YUI_datatable(data, fields, column_defs, counter, sort_key, ${c.visual.admin_grid_items});
+</script>
+</%def>
--- a/rhodecode/templates/admin/repos/repo_add.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/repos/repo_add.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Add repository')} &middot; ${c.rhodecode_name}
+    ${_('Add repository')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -16,7 +19,7 @@
     ${_('Repositories')}
     %endif
     &raquo;
-    ${_('Add new')}
+    ${_('Add Repository')}
 </%def>
 
 <%def name="page_nav()">
--- a/rhodecode/templates/admin/repos/repo_add_base.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/repos/repo_add_base.html	Wed Jul 02 19:03:13 2014 -0400
@@ -10,12 +10,15 @@
             </div>
             <div class="input">
                 ${h.text('repo_name',class_="small")}
+                <div style="margin: 6px 0px 0px 0px">
+                    <a id="remote_clone_toggle" href="#"><i class="icon-download-alt"></i> ${_('Import existing repository ?')}</a>
+                </div>
                 %if not c.rhodecode_user.is_admin:
                     ${h.hidden('user_created',True)}
                 %endif
             </div>
          </div>
-        <div class="field">
+        <div id="remote_clone" class="field" style="display: none">
             <div class="label">
                 <label for="clone_uri">${_('Clone from')}:</label>
             </div>
@@ -23,8 +26,17 @@
                 ${h.text('clone_uri',class_="small")}
                 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
             </div>
-         </div>
-         <div class="field">
+        </div>
+        <div class="field">
+            <div class="label label-textarea">
+                <label for="repo_description">${_('Description')}:</label>
+            </div>
+            <div class="textarea-repo editor">
+                ${h.textarea('repo_description')}
+                <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
+            </div>
+        </div>
+        <div class="field">
              <div class="label">
                  <label for="repo_group">${_('Repository group')}:</label>
              </div>
@@ -32,7 +44,16 @@
                  ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
                  <span class="help-block">${_('Optionaly select a group to put this repository into.')}</span>
              </div>
-         </div>
+        </div>
+        <div id="copy_perms" class="field">
+            <div class="label label-checkbox">
+                <label for="repo_copy_permissions">${_('Copy parent group permissions')}:</label>
+            </div>
+            <div class="checkboxes">
+                ${h.checkbox('repo_copy_permissions',value="True")}
+                <span class="help-block">${_('Copy permission set from parent repository group.')}</span>
+            </div>
+        </div>
         <div class="field">
             <div class="label">
                 <label for="repo_type">${_('Type')}:</label>
@@ -41,26 +62,17 @@
                 ${h.select('repo_type','hg',c.backends,class_="small")}
                 <span class="help-block">${_('Type of repository to create.')}</span>
             </div>
-         </div>
-         <div class="field">
+        </div>
+        <div class="field">
             <div class="label">
                 <label for="repo_landing_rev">${_('Landing revision')}:</label>
             </div>
             <div class="input">
                 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
-                <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
+                <span class="help-block">${_('Default revision for files page, downloads, full text search index and readme generation')}</span>
             </div>
         </div>
         <div class="field">
-            <div class="label label-textarea">
-                <label for="repo_description">${_('Description')}:</label>
-            </div>
-            <div class="textarea text-area editor">
-                ${h.textarea('repo_description')}
-                <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
-            </div>
-         </div>
-        <div class="field">
             <div class="label label-checkbox">
                 <label for="repo_private">${_('Private repository')}:</label>
             </div>
@@ -68,10 +80,49 @@
                 ${h.checkbox('repo_private',value="True")}
                 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
             </div>
-         </div>
+        </div>
         <div class="buttons">
-          ${h.submit('add',_('Add'),class_="ui-btn large")}
+          ${h.submit('add',_('Add'),class_="btn")}
         </div>
     </div>
 </div>
+<script>
+    $(document).ready(function(){
+        var setCopyPermsOption = function(group_val){
+            if(group_val != "-1"){
+                $('#copy_perms').show()
+            }
+            else{
+                $('#copy_perms').hide();
+            }
+        }
+
+        $('#remote_clone_toggle').on('click', function(e){
+            $('#remote_clone').show();
+            e.preventDefault();
+        })
+        if($('#remote_clone input').hasClass('error')){
+            $('#remote_clone').show();
+        }
+        if($('#remote_clone input').val()){
+            $('#remote_clone').show();
+        }
+        $("#repo_group").select2({
+            'dropdownAutoWidth': true
+        });
+
+        setCopyPermsOption($('#repo_group').val())
+        $("#repo_group").on("change", function(e) {
+            setCopyPermsOption(e.val)
+        })
+
+        $("#repo_type").select2({
+            'minimumResultsForSearch': -1,
+        });
+        $("#repo_landing_rev").select2({
+            'minimumResultsForSearch': -1,
+        });
+        $('#repo_name').focus();
+    })
+</script>
 ${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos/repo_creating.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,69 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+## don't trigger flash messages on this page
+<%def name="flash_msg()">
+</%def>
+
+<%def name="title()">
+    ${_('%s Creating repository') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${_('Creating repository')} ${c.repo}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('repositories')}
+</%def>
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+
+    <div style="display:table; padding: 10px 0px; font-size: 14px;font-weight: bold;margin-right: auto;margin-left: auto">
+        ${_('Repository "%(repo_name)s" is beeing created, you will be redirected when this process is finished.' % {'repo_name':c.repo_name})}
+    </div>
+
+    <div id="progress" style="width: 500px;margin-left: auto; margin-right: auto">
+        <div class="progress progress-striped active">
+          <div class="progress-bar progress-bar" role="progressbar"
+               aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%">
+          </div>
+        </div>
+    </div>
+    <div id="progress_error" style="display: none;">
+        <div style="font-weight: bold; color:#aa1111">
+        ${_("We're sorry but error occured during this operation. Please check your RhodeCode server logs, or contact administrator.")}
+        </div>
+    </div>
+</div>
+</%def>
+
+<script>
+(function worker() {
+  $.ajax({
+    url: '${h.url('repo_check_home', repo_name=c.repo_name, repo=c.repo, task_id=c.task_id)}',
+    success: function(data) {
+      if(data.result === true){
+          //redirect to created fork if our ajax loop tells us to do so.
+          window.location = "${h.url('summary_home', repo_name = c.repo)}";
+      }
+    },
+    complete: function(resp, status) {
+      if (resp.status == 200){
+          // Schedule the next request when the current one's complete
+          setTimeout(worker, 1000);
+      }
+      else{
+          $("#progress").html($('#progress_error').html())
+      }
+    }
+  });
+})();
+</script>
--- a/rhodecode/templates/admin/repos/repo_edit.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/repos/repo_edit.html	Wed Jul 02 19:03:13 2014 -0400
@@ -5,7 +5,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Edit repository')} ${c.repo_info.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s repository settings') % c.repo_info.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -13,376 +16,56 @@
 </%def>
 
 <%def name="page_nav()">
-    ${self.menu('admin')}
+    ${self.menu('repositories')}
 </%def>
 
 <%def name="main()">
 ${self.repo_context_bar('options')}
-<div class="box box-left">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-            <div class="field">
-                <div class="label">
-                    <label for="repo_name">${_('Name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('repo_name',class_="medium")}
-                    <span class="help-block">${_('Non-changeable id')}: ${c.repo_info.repo_id}</span>
-                </div>
-           </div>
-           <div class="field">
-               <div class="label">
-                   <label for="clone_uri">${_('Clone uri')}:</label>
-               </div>
-               <div class="input">
-                   ${h.text('clone_uri',class_="medium")}
-                 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
-               </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")}
-                    <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
-                </div>
-            </div>
-            <div class="field">
-                <div class="label">
-                    <label for="repo_landing_rev">${_('Landing revision')}:</label>
-                </div>
-                <div class="input">
-                    ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
-                    <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
-                </div>
-            </div>
-            <div class="field">
-                <div class="label label-textarea">
-                    <label for="repo_description">${_('Description')}:</label>
-                </div>
-                <div class="textarea text-area editor">
-                    ${h.textarea('repo_description')}
-                    <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
-                </div>
-            </div>
+<div class="box">
+    <!--<div class="title">-->
+        <!--${self.breadcrumbs()}-->
+    <!--</div>-->
 
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="repo_private">${_('Private repository')}:</label>
-                </div>
-                <div class="checkboxes">
-                    ${h.checkbox('repo_private',value="True")}
-                    <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
-                </div>
-            </div>
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
-                </div>
-                <div class="checkboxes">
-                    ${h.checkbox('repo_enable_statistics',value="True")}
-                    <span class="help-block">${_('Enable statistics window on summary page.')}</span>
-                </div>
-            </div>
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
-                </div>
-                <div class="checkboxes">
-                    ${h.checkbox('repo_enable_downloads',value="True")}
-                    <span class="help-block">${_('Enable download menu on summary page.')}</span>
-                </div>
-            </div>
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="repo_enable_locking">${_('Enable locking')}:</label>
-                </div>
-                <div class="checkboxes">
-                    ${h.checkbox('repo_enable_locking',value="True")}
-                    <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
-                </div>
-            </div>
-            <div class="field">
-                <div class="label">
-                    <label for="user">${_('Owner')}:</label>
-                </div>
-                <div class="input input-medium ac">
-                    <div class="perm_ac">
-                       ${h.text('user',class_='yui-ac-input')}
-                       <span class="help-block">${_('Change owner of this repository.')}</span>
-                       <div id="owner_container"></div>
-                    </div>
-                </div>
-             </div>
-            %if c.visual.repository_fields:
-              ## EXTRA FIELDS
-              %for field in c.repo_fields:
-                <div class="field">
-                    <div class="label">
-                        <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
-                    </div>
-                    <div class="input input-medium">
-                        ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
-                        %if field.field_desc:
-                          <span class="help-block">${field.field_desc}</span>
-                        %endif
-                    </div>
-                 </div>
-              %endfor
-            %endif
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-</div>
-
-<div class="box box-right">
-    <div class="title">
-        <h5>${_('Permissions')}</h5>
-    </div>
-    ${h.form(url('set_repo_perm_member', repo_name=c.repo_info.repo_name),method='post')}
-    <div class="form">
-       <div class="fields">
-            <div class="field">
-                <div class="label">
-                    <label for="input">${_('Permissions')}:</label>
-                </div>
-                <div class="input">
-                    ${h.hidden('repo_private')}
-                    <%include file="repo_edit_perms.html"/>
-                </div>
-            </div>
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-            </div>
-       </div>
-    </div>
-    ${h.end_form()}
-</div>
-
-
-<div class="box box-right"  style="clear:right">
-    <div class="title">
-        <h5>${_('Advanced settings')}</h5>
+    ##main
+    <div style="width: 150px; float:left">
+        <ul class="nav nav-pills nav-stacked">
+          <!--<li>-->
+           <!--<div class="gravatar_box" style="height: 26px">-->
+             <!--<div class="gravatar" style="float: left">-->
+                <!--<i class="icon-group" style="font-size: 26px"></i>-->
+             <!--</div>-->
+               <!--<div style="margin:10px 0px 10px 0px; color:#5f5f5f; float:left">-->
+                <!--<strong>${'repo-info'}</strong>-->
+               <!--</div>-->
+           <!--</div>-->
+          <!--</li>-->
+          <li class="${'active' if c.active=='settings' else ''}">
+              <a href="${h.url('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
+          </li>
+          <li class="${'active' if c.active=='permissions' else ''}">
+              <a href="${h.url('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
+          </li>
+          <li class="${'active' if c.active=='advanced' else ''}">
+              <a href="${h.url('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
+          </li>
+          <li class="${'active' if c.active=='fields' else ''}">
+              <a href="${h.url('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra fields')}</a>
+          </li>
+          <li class="${'active' if c.active=='caches' else ''}">
+              <a href="${h.url('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
+          </li>
+          <li class="${'active' if c.active=='remote' else ''}">
+              <a href="${h.url('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
+          </li>
+          <li class="${'active' if c.active=='statistics' else ''}">
+              <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
+          </li>
+        </ul>
     </div>
 
-    <h3>${_('Statistics')}</h3>
-    ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
-    <div class="form">
-       <div class="fields">
-           ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
-           <div class="field" style="border:none;color:#888">
-           <ul>
-                <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
-                <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
-           </ul>
-           </div>
-       </div>
-    </div>
-    ${h.end_form()}
-
-    %if c.repo_info.clone_uri:
-    <h3>${_('Remote')}</h3>
-    ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
-    <div class="form">
-       <div class="fields">
-           ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
-           <div class="field" style="border:none">
-           <ul>
-                <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
-           </ul>
-           </div>
-       </div>
-    </div>
-    ${h.end_form()}
-    %endif
-
-    <h3>${_('Cache')}</h3>
-    ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
-    <div class="form">
-       <div class="fields">
-           ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
-          <div class="field" style="border:none;color:#888">
-          <ul>
-              <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
-              </li>
-          </ul>
-          </div>
-          <div class="field" style="border:none;">
-            ${_('List of cached values')}
-               <table>
-               <tr>
-                <th>${_('Prefix')}</th>
-                <th>${_('Key')}</th>
-                <th>${_('Active')}</th>
-                </tr>
-              %for cache in c.repo_info.cache_keys:
-                  <tr>
-                    <td>${cache.get_prefix() or '-'}</td>
-                    <td>${cache.cache_key}</td>
-                    <td>${h.boolicon(cache.cache_active)}</td>
-                  </tr>
-              %endfor
-              </table>
-          </div>
-       </div>
+    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
+        <%include file="/admin/repos/repo_edit_${c.active}.html"/>
     </div>
-    ${h.end_form()}
-
-    <h3>${_('Public journal')}</h3>
-    ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
-    <div class="form">
-      ${h.hidden('auth_token',str(h.get_token()))}
-      <div class="field">
-      %if c.in_public_journal:
-        ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
-      %else:
-        ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
-      %endif
-      </div>
-     <div class="field" style="border:none;color:#888">
-     <ul>
-          <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
-          </li>
-     </ul>
-     </div>
-    </div>
-    ${h.end_form()}
-
-    <h3>${_('Locking')}</h3>
-    ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
-    <div class="form">
-       <div class="fields">
-          %if c.repo_info.locked[0]:
-           ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
-           ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
-          %else:
-            ${h.submit('set_lock',_('Lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
-            ${_('Repository is not locked')}
-          %endif
-       </div>
-       <div class="field" style="border:none;color:#888">
-       <ul>
-            <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
-            </li>
-       </ul>
-       </div>
-    </div>
-    ${h.end_form()}
-
-    <h3>${_('Set as fork of')}</h3>
-    ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
-    <div class="form">
-       <div class="fields">
-           ${h.select('id_fork_of','',c.repos_list,class_="medium")}
-           ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="ui-btn",)}
-       </div>
-           <div class="field" style="border:none;color:#888">
-           <ul>
-                <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
-           </ul>
-           </div>
-    </div>
-    ${h.end_form()}
-
-    <h3>${_('Delete')}</h3>
-    ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
-    <div class="form">
-        <div class="fields">
-            <div class="field" style="border:none;color:#888">
-##              <div class="label">
-##                  <label for="">${_('Remove repository')}:</label>
-##              </div>
-                <div class="checkboxes">
-                    ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
-                    %if c.repo_info.forks.count():
-                        - ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
-                        <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
-                        <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
-                    %endif
-                    <ul>
-                        <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}</li>
-                    </ul>
-                </div>
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
 </div>
 
-##TODO: this should be controlled by the VISUAL setting
-%if c.visual.repository_fields:
-<div class="box box-left" style="clear:left">
-    <!-- box / title -->
-    <div class="title">
-        <h5>${_('Extra fields')}</h5>
-    </div>
-
-    <div class="emails_wrap">
-      <table class="noborder">
-      %for field in c.repo_fields:
-        <tr>
-            <td>${field.field_label} (${field.field_key})</td>
-            <td>${field.field_type}</td>
-            <td>
-              ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
-                  ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id,
-                  class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
-              ${h.end_form()}
-            </td>
-        </tr>
-      %endfor
-      </table>
-    </div>
-
-    ${h.form(url('create_repo_fields', repo_name=c.repo_info.repo_name),method='put')}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="new_field_key">${_('New field key')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('new_field_key', class_='small')}
-                </div>
-             </div>
-             <div class="field">
-                <div class="label">
-                    <label for="new_field_label">${_('New field label')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))}
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label">
-                    <label for="new_field_desc">${_('New field description')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))}
-                </div>
-             </div>
-
-            <div class="buttons">
-              ${h.submit('save',_('Add'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-</div>
-%endif
 </%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos/repo_edit_advanced.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,97 @@
+<h3>${_('Fork of')}</h3>
+${h.form(url('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='put')}
+<div class="form">
+   <div class="fields">
+       ${h.select('id_fork_of','',c.repos_list,class_="medium")}
+       ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-small",)}
+   </div>
+       <div class="field" style="border:none;color:#888">
+       <ul>
+            <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
+       </ul>
+       </div>
+</div>
+${h.end_form()}
+
+<script>
+    $(document).ready(function(){
+        $("#id_fork_of").select2({
+            'dropdownAutoWidth': true,
+        });
+    })
+</script>
+
+<h3>${_('Public journal visibility')}</h3>
+${h.form(url('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='put')}
+<div class="form">
+  ${h.hidden('auth_token',str(h.get_token()))}
+  <div class="field">
+  %if c.in_public_journal:
+    <button class="btn btn-small" type="submit">
+        <i class="icon-minus"></i>
+        ${_('Remove from public journal')}
+    </button>
+  %else:
+    <button class="btn btn-small" type="submit">
+        <i class="icon-plus"></i>
+        ${_('Add to public journal')}
+    </button>
+  %endif
+  </div>
+ <div class="field" style="border:none;color:#888">
+ <ul>
+      <li>${_('All actions made on this repository will be accessible to everyone in public journal')}</li>
+ </ul>
+ </div>
+</div>
+${h.end_form()}
+
+<h3>${_('Change locking')}</h3>
+${h.form(url('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='put')}
+<div class="form">
+   <div class="fields">
+      %if c.repo_info.locked[0]:
+        ${h.hidden('set_unlock', '1')}
+        <button class="btn btn-small" type="submit"
+                onclick="return confirm('${_('Confirm to unlock repository')}');">
+            <i class="icon-unlock"></i>
+            ${_('Unlock repository')}
+        </button>
+       ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
+      %else:
+        ${h.hidden('set_lock', '1')}
+        <button class="btn btn-small" type="submit"
+                onclick="return confirm('${_('Confirm to lock repository')}');">
+            <i class="icon-lock"></i>
+            ${_('Lock repository')}
+        </button>
+        ${_('Repository is not locked')}
+      %endif
+   </div>
+   <div class="field" style="border:none;color:#888">
+   <ul>
+        <li>${_('Force locking on repository. Works only when anonymous access is disabled. Trigering a pull locks repository by user who pulled, only the same user can unlock by doing a push')}
+        </li>
+   </ul>
+   </div>
+</div>
+${h.end_form()}
+
+<h3>${_('Delete')}</h3>
+${h.form(url('repo', repo_name=c.repo_name),method='delete')}
+    <button class="btn btn-small btn-danger" type="submit"
+            onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
+        <i class="icon-remove-sign"></i>
+        ${_('Delete this repository')}
+    </button>
+    %if c.repo_info.forks.count():
+        ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
+        <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
+        <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
+    %endif
+    <div class="field" style="border:none;color:#888">
+        <ul>
+        <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}</li>
+        </ul>
+    </div>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos/repo_edit_caches.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,30 @@
+${h.form(url('edit_repo_caches', repo_name=c.repo_name), method='put')}
+<div class="form">
+   <div class="fields">
+       ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
+      <div class="field" style="border:none;color:#888">
+      <ul>
+          <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
+          </li>
+      </ul>
+      </div>
+      <div class="field" style="border:none;">
+        ${_('List of cached values')}
+           <table>
+           <tr>
+            <th>${_('Prefix')}</th>
+            <th>${_('Key')}</th>
+            <th>${_('Active')}</th>
+            </tr>
+          %for cache in c.repo_info.cache_keys:
+              <tr>
+                <td>${cache.get_prefix() or '-'}</td>
+                <td>${cache.cache_key}</td>
+                <td>${h.boolicon(cache.cache_active)}</td>
+              </tr>
+          %endfor
+          </table>
+      </div>
+   </div>
+</div>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos/repo_edit_fields.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,68 @@
+%if c.visual.repository_fields:
+    %if c.repo_fields:
+    <div class="emails_wrap">
+      <table class="noborder">
+        <th>${_('Label')}</th>
+        <th>${_('Key')}</th>
+        <th>${_('Type')}</th>
+        <th>${_('Action')}</th>
+
+      %for field in c.repo_fields:
+        <tr>
+            <td>${field.field_label}</td>
+            <td>${field.field_key}</td>
+            <td>${field.field_type}</td>
+            <td>
+              ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
+                  <i class="icon-remove-sign" style="color:#FF4444"></i>
+                  ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id,
+                  class_="action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
+              ${h.end_form()}
+            </td>
+        </tr>
+      %endfor
+      </table>
+    </div>
+    %endif
+    ${h.form(url('create_repo_fields', repo_name=c.repo_name),method='put')}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+             <div class="field">
+                <div class="label">
+                    <label for="new_field_key">${_('New field key')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('new_field_key', class_='small')}
+                </div>
+             </div>
+             <div class="field">
+                <div class="label">
+                    <label for="new_field_label">${_('New field label')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label">
+                    <label for="new_field_desc">${_('New field description')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))}
+                </div>
+             </div>
+
+            <div class="buttons">
+              ${h.submit('save',_('Add'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
+            </div>
+        </div>
+    </div>
+    ${h.end_form()}
+%else:
+  <div style="font-size: 20px">
+    ${_('Extra fields are disabled')}
+  </div>
+%endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos/repo_edit_fork.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,21 @@
+${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
+<div class="form">
+   <div class="fields">
+       ${h.select('id_fork_of','',c.repos_list,class_="medium")}
+       ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-small",)}
+   </div>
+       <div class="field" style="border:none;color:#888">
+       <ul>
+            <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
+       </ul>
+       </div>
+</div>
+${h.end_form()}
+
+<script>
+    $(document).ready(function(){
+        $("#id_fork_of").select2({
+            'dropdownAutoWidth': true,
+        });
+    })
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos/repo_edit_permissions.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,125 @@
+${h.form(url('edit_repo_perms_update', repo_name=c.repo_name), method='put')}
+<div class="form">
+   <div class="fields">
+        <div class="field">
+            ${h.hidden('repo_private')}
+            <table id="permissions_manage" class="noborder">
+                <tr>
+                    <td>${_('none')}</td>
+                    <td>${_('read')}</td>
+                    <td>${_('write')}</td>
+                    <td>${_('admin')}</td>
+                    <td>${_('user/user group')}</td>
+                    <td></td>
+                </tr>
+                ## USERS
+                %for r2p in c.repo_info.repo_to_perm:
+                    %if r2p.user.username =='default' and c.repo_info.private:
+                        <tr>
+                            <td colspan="4">
+                                <span class="private_repo_msg">
+                                ${_('private repository')}
+                                </span>
+                            </td>
+                            <td class="private_repo_msg"><i class="icon-user"></i> ${_('default')}</td>
+                        </tr>
+                    %else:
+                    <tr id="id${id(r2p.user.username)}">
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
+                        <td style="white-space: nowrap;">
+                            <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>
+                            %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default':
+                             <a href="${h.url('edit_user',id=r2p.user.user_id)}">${r2p.user.username}</a>
+                            %else:
+                             ${r2p.user.username if r2p.user.username != 'default' else _('default')}
+                            %endif
+                        </td>
+                        <td>
+                          %if r2p.user.username !='default':
+                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
+                            <i class="icon-remove"></i> ${_('revoke')}
+                            </span>
+                          %endif
+                        </td>
+                    </tr>
+                    %endif
+                %endfor
+
+                ## USER GROUPS
+                %for g2p in c.repo_info.users_group_to_perm:
+                    <tr id="id${id(g2p.users_group.users_group_name)}">
+                        <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
+                        <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
+                        <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
+                        <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
+                        <td style="white-space: nowrap;">
+                            <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
+                            %if h.HasPermissionAny('hg.admin')():
+                             <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">${g2p.users_group.users_group_name}</a>
+                            %else:
+                             ${g2p.users_group.users_group_name}
+                            %endif
+                        </td>
+                        <td>
+                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}', '${g2p.users_group.users_group_name}')">
+                            <i class="icon-remove"></i> ${_('revoke')}
+                            </span>
+                        </td>
+                    </tr>
+                %endfor
+
+                <%
+                _tmpl = h.literal("""' \
+                    <td><input type="radio" value="repository.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td><input type="radio" value="repository.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td><input type="radio" value="repository.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td><input type="radio" value="repository.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td class="ac"> \
+                        <div class="perm_ac" id="perm_ac_{0}"> \
+                            <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
+                            <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">  \
+                            <div id="perm_container_{0}"></div> \
+                        </div> \
+                    </td> \
+                    <td></td>'""")
+                %>
+                ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
+                <tr class="new_members last_new_member" id="add_perm_input"></tr>
+                <tr>
+                    <td colspan="6">
+                        <span id="add_perm" style="cursor: pointer;">
+                            <i class="icon-plus"></i> ${_('Add new')}
+                        </span>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div class="buttons">
+          ${h.submit('save',_('Save'),class_="btn")}
+          ${h.reset('reset',_('Reset'),class_="btn")}
+        </div>
+   </div>
+</div>
+${h.end_form()}
+
+<script type="text/javascript">
+    function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
+        url = "${h.url('edit_repo_perms_revoke',repo_name=c.repo_name)}";
+        var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
+        if (confirm(revoke_msg)){
+            ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
+        }
+    };
+
+    YUE.onDOMReady(function () {
+        if (!YUD.hasClass('perm_new_member_name', 'error')) {
+            YUD.setStyle('add_perm_input', 'display', 'none');
+        }
+        YAHOO.util.Event.addListener('add_perm', 'click', function () {
+            addPermAction(${_tmpl}, ${c.users_array|n}, ${c.user_groups_array|n});
+        });
+    });
+</script>
--- a/rhodecode/templates/admin/repos/repo_edit_perms.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-<table id="permissions_manage" class="noborder">
-    <tr>
-        <td>${_('none')}</td>
-        <td>${_('read')}</td>
-        <td>${_('write')}</td>
-        <td>${_('admin')}</td>
-        <td>${_('member')}</td>
-        <td></td>
-    </tr>
-    ## USERS
-    %for r2p in c.repo_info.repo_to_perm:
-        %if r2p.user.username =='default' and c.repo_info.private:
-            <tr>
-                <td colspan="4">
-                    <span class="private_repo_msg">
-                    ${_('private repository')}
-                    </span>
-                </td>
-                <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${_('default')}</td>
-            </tr>
-        %else:
-        <tr id="id${id(r2p.user.username)}">
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
-            <td style="white-space: nowrap;">
-                <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
-            </td>
-            <td>
-              %if r2p.user.username !='default':
-                <span class="delete_icon action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
-                ${_('revoke')}
-                </span>
-              %endif
-            </td>
-        </tr>
-        %endif
-    %endfor
-
-    ## USER GROUPS
-    %for g2p in c.repo_info.users_group_to_perm:
-        <tr id="id${id(g2p.users_group.users_group_name)}">
-            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
-            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
-            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
-            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
-            <td style="white-space: nowrap;">
-                <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
-                %if h.HasPermissionAny('hg.admin')():
-                 <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">${g2p.users_group.users_group_name}</a>
-                %else:
-                 ${g2p.users_group.users_group_name}
-                %endif
-            </td>
-            <td>
-                <span class="delete_icon action_button" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}', '${g2p.users_group.users_group_name}')">
-                ${_('revoke')}
-                </span>
-            </td>
-        </tr>
-    %endfor
-    <%
-    _tmpl = h.literal("""' \
-        <td><input type="radio" value="repository.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td><input type="radio" value="repository.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td><input type="radio" value="repository.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td><input type="radio" value="repository.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td class="ac"> \
-            <div class="perm_ac" id="perm_ac_{0}"> \
-                <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
-                <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">  \
-                <div id="perm_container_{0}"></div> \
-            </div> \
-        </td> \
-        <td></td>'""")
-    %>
-    ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
-    <tr class="new_members last_new_member" id="add_perm_input"></tr>
-    <tr>
-        <td colspan="6">
-            <span id="add_perm" class="add_icon" style="cursor: pointer;">
-            ${_('Add another member')}
-            </span>
-        </td>
-    </tr>
-</table>
-<script type="text/javascript">
-function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
-    url = "${h.url('delete_repo_perm_member',repo_name=c.repo_name)}";
-    var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
-    if (confirm(revoke_msg)){
-        ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
-    }
-};
-
-YUE.onDOMReady(function () {
-    if (!YUD.hasClass('perm_new_member_name', 'error')) {
-        YUD.setStyle('add_perm_input', 'display', 'none');
-    }
-    YAHOO.util.Event.addListener('add_perm', 'click', function () {
-        addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
-    });
-});
-
-</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos/repo_edit_remote.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,16 @@
+%if c.repo_info.clone_uri:
+<div style="font-size: 20px; padding: 0px 0px 10px 0px">
+   ${_('Remote url')}: <a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri_hidden}</a></li>
+</div>
+${h.form(url('edit_repo_remote', repo_name=c.repo_name), method='put')}
+<div class="form">
+   <div class="fields">
+       ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
+   </div>
+</div>
+${h.end_form()}
+%else:
+  <div style="font-size: 20px">
+    ${_('This repository does not have any remote url set')}
+  </div>
+%endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos/repo_edit_settings.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,164 @@
+${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+            <div class="field">
+                <div class="label">
+                    <label for="repo_name">${_('Name')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('repo_name',class_="medium")}
+                    <span class="help-block">${_('Non-changeable id')}: `_${c.repo_info.repo_id}` <span><a id="show_more_clone_id" href="#">${_('what is that ?')}</a></span></span>
+                    <span id="clone_id" class="help-block" style="display: none">
+                        ${_('URL by id')}: `${c.repo_info.clone_url(with_id=True)}` </br>
+                        ${_('''In case this repository is renamed or moved into another group the repository url changes.
+                               Using above url guarantees that this repository will allways be accessible under such url.
+                               Usefull for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</span>
+                </div>
+           </div>
+           <div class="field">
+               <div class="label">
+                   <label for="clone_uri">${_('Clone uri')}:</label>
+               </div>
+               <div class="input">
+                   %if c.repo_info.clone_uri:
+                    <div id="clone_uri_hidden" style="font-size: 14px">
+                        <span id="clone_uri_hidden_value">${c.repo_info.clone_uri_hidden}</span>
+                        <span style="cursor: pointer; padding: 0px 0px 5px 0px" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
+                    </div>
+                    <div id="alter_clone_uri" style="display: none">
+                        ${h.text('clone_uri',class_="medium",  placeholder=_('new value'))}
+                    </div>
+                   %else:
+                    ## not set yet, display form to set it
+                    ${h.text('clone_uri',class_="medium")}
+                    ${h.hidden('clone_uri_change', 'NEW')}
+                   %endif
+                 <span id="alter_clone_uri_help_block" class="help-block">${_('http[s] url used for doing remote pulls.')}</span>
+               </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")}
+                    <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
+                </div>
+            </div>
+            <div class="field">
+                <div class="label">
+                    <label for="repo_landing_rev">${_('Landing revision')}:</label>
+                </div>
+                <div class="input">
+                    ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
+                    <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
+                </div>
+            </div>
+            <div class="field">
+                <div class="label">
+                    <label for="user">${_('Owner')}:</label>
+                </div>
+                <div class="input input-medium ac">
+                    <div class="perm_ac">
+                       ${h.text('user',class_='yui-ac-input')}
+                       <span class="help-block">${_('Change owner of this repository.')}</span>
+                       <div id="owner_container"></div>
+                    </div>
+                </div>
+             </div>
+            <div class="field">
+                <div class="label label-textarea">
+                    <label for="repo_description">${_('Description')}:</label>
+                </div>
+                <div class="textarea text-area editor">
+                    ${h.textarea('repo_description', style="height:165px")}
+                    <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
+                </div>
+            </div>
+
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="repo_private">${_('Private repository')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('repo_private',value="True")}
+                    <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
+                </div>
+            </div>
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('repo_enable_statistics',value="True")}
+                    <span class="help-block">${_('Enable statistics window on summary page.')}</span>
+                </div>
+            </div>
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('repo_enable_downloads',value="True")}
+                    <span class="help-block">${_('Enable download menu on summary page.')}</span>
+                </div>
+            </div>
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="repo_enable_locking">${_('Enable locking')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('repo_enable_locking',value="True")}
+                    <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
+                </div>
+            </div>
+
+            %if c.visual.repository_fields:
+              ## EXTRA FIELDS
+              %for field in c.repo_fields:
+                <div class="field">
+                    <div class="label">
+                        <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
+                    </div>
+                    <div class="input input-medium">
+                        ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
+                        %if field.field_desc:
+                          <span class="help-block">${field.field_desc}</span>
+                        %endif
+                    </div>
+                 </div>
+              %endfor
+            %endif
+            <div class="buttons">
+              ${h.submit('save',_('Save'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
+            </div>
+        </div>
+    </div>
+    ${h.end_form()}
+
+<script>
+    $(document).ready(function(){
+        $('#show_more_clone_id').on('click', function(e){
+            $('#clone_id').show();
+            e.preventDefault();
+        })
+        $('#edit_clone_uri').on('click', function(e){
+          $('#alter_clone_uri').show();
+          $('#edit_clone_uri').hide();
+          $('#clone_uri_hidden').hide();
+          ## store hash of old value for change detection
+          var uri_change =  '<input id="clone_uri_change" name="clone_uri_change" type="hidden" value="${h.md5(c.repo_info.clone_uri or "").hexdigest()}" />';
+          $('#alter_clone_uri_help_block').html($('#alter_clone_uri_help_block').html()+" ("+$('#clone_uri_hidden_value').html()+")")
+        })
+
+        $('#repo_landing_rev').select2({
+            'dropdownAutoWidth': true
+        });
+        $('#repo_group').select2({
+            'dropdownAutoWidth': true
+        });
+
+    })
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos/repo_edit_statistics.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,13 @@
+${h.form(url('edit_repo_statistics', repo_name=c.repo_info.repo_name), method='put')}
+<div class="form">
+    <div class="fields">
+       <div class="field" style="border:none;color:#888">
+        <ul>
+            <li>${_('Processed commits')}: ${c.stats_revision}/${c.repo_last_rev}</li>
+            <li>${_('Processed progress')}: ${c.stats_percentage}%</li>
+        </ul>
+       </div>
+        ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset statistics'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
+   </div>
+</div>
+${h.end_form()}
--- a/rhodecode/templates/admin/repos/repos.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/repos/repos.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Repositories administration')} &middot; ${c.rhodecode_name}
+    ${_('Repositories administration')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -19,112 +22,41 @@
         <ul class="links">
          %if h.HasPermissionAny('hg.admin','hg.create.repository')():
           <li>
-            <span>${h.link_to(_(u'Add repository'),h.url('new_repo'))}</span>
+            <a href="${h.url('new_repo')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_(u'Add Repository')}</a>
           </li>
          %endif
         </ul>
     </div>
-    <div class="table yui-skin-sam" id="repos_list_wrap"></div>
+    <div class="table-grid table yui-skin-sam" id="datatable_list_wrap"></div>
     <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
 
 
 </div>
 <script>
-  var url = "${h.url('formatted_users', format='json')}";
   var data = ${c.data|n};
-  var myDataSource = new YAHOO.util.DataSource(data);
-  myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
-
-  myDataSource.responseSchema = {
-      resultsList: "records",
-      fields: [
-         {key:"menu"},
-         {key:"raw_name"},
-         {key:"name"},
-         {key:"desc"},
-         {key:"last_changeset"},
-         {key:"owner"},
-         {key:"action"},
-      ]
-   };
-  myDataSource.doBeforeCallback = function(req,raw,res,cb) {
-      // This is the filter function
-      var data     = res.results || [],
-          filtered = [],
-          i,l;
-
-      if (req) {
-          req = req.toLowerCase();
-          for (i = 0; i<data.length; i++) {
-              var pos = data[i].raw_name.toLowerCase().indexOf(req)
-              if (pos != -1) {
-                  filtered.push(data[i]);
-              }
-          }
-          res.results = filtered;
-      }
-      YUD.get('repo_count').innerHTML = res.results.length;
-      return res;
-  }
-
-  // main table sorting
-  var myColumnDefs = [
-      {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
-      {key:"name",label:"${_('Name')}",sortable:true,
-          sortOptions: { sortFunction: nameSort }},
-      {key:"desc",label:"${_('Description')}",sortable:true},
-      {key:"last_changeset",label:"${_('Tip')}",sortable:true,
-          sortOptions: { sortFunction: revisionSort }},
-      {key:"owner",label:"${_('Owner')}",sortable:true},
-      {key:"action",label:"${_('Action')}",sortable:false},
+  var fields = [
+    {key:"menu"},
+    {key:"raw_name"},
+    {key:"name"},
+    {key:"desc"},
+    {key:"last_changeset"},
+    {key:"last_rev_raw"},
+    {key:"owner"},
+    {key:"state"},
+    {key:"action"},
   ];
-
-  var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
-    sortedBy:{key:"name",dir:"asc"},
-    paginator: YUI_paginator(25, ['user-paginator']),
-
-    MSG_SORTASC:"${_('Click to sort ascending')}",
-    MSG_SORTDESC:"${_('Click to sort descending')}",
-    MSG_EMPTY:"${_('No records found.')}",
-    MSG_ERROR:"${_('Data error.')}",
-    MSG_LOADING:"${_('Loading...')}",
-  }
-  );
-  myDataTable.subscribe('postRenderEvent',function(oArgs) {
-      tooltip_activate();
-      quick_repo_menu();
-  });
-
-  var filterTimeout = null;
-
-  updateFilter  = function () {
-      // Reset timeout
-      filterTimeout = null;
-
-      // Reset sort
-      var state = myDataTable.getState();
-      state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
-
-      // Get filtered data
-      myDataSource.sendRequest(YUD.get('q_filter').value,{
-          success : myDataTable.onDataReturnInitializeTable,
-          failure : myDataTable.onDataReturnInitializeTable,
-          scope   : myDataTable,
-          argument: state
-      });
-
-  };
-  YUE.on('q_filter','click',function(){
-      if(!YUD.hasClass('q_filter', 'loaded')){
-          //TODO: load here full list later to do search within groups
-          YUD.addClass('q_filter', 'loaded');
-      }
-   });
-
-  YUE.on('q_filter','keyup',function (e) {
-      clearTimeout(filterTimeout);
-      filterTimeout = setTimeout(updateFilter,600);
-  });
+  var column_defs = [
+    {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
+    {key:"name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: nameSort }},
+    {key:"desc",label:"${_('Description')}",sortable:true},
+    {key:"last_changeset",label:"${_('Tip')}",sortable:true, sortOptions: { sortFunction: revisionSort }},
+    {key:"owner",label:"${_('Owner')}",sortable:true},
+    {key:"state",label:"${_('State')}",sortable:true},
+    {key:"action",label:"${_('Action')}",sortable:false},
+  ];
+  var counter = YUD.get('repo_count');
+  var sort_key = "name";
+  YUI_datatable(data, fields, column_defs, counter, sort_key, ${c.visual.admin_grid_items});
 </script>
 
 </%def>
--- a/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-<table id="permissions_manage" class="noborder">
-    <tr>
-        <td>${_('none')}</td>
-        <td>${_('read')}</td>
-        <td>${_('write')}</td>
-        <td>${_('admin')}</td>
-        <td>${_('member')}</td>
-        <td></td>
-    </tr>
-    ## USERS
-    %for r2p in c.repos_group.repo_group_to_perm:
-        ##forbid revoking permission from yourself
-        <tr id="id${id(r2p.user.username)}">
-            %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin:
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
-            <td style="white-space: nowrap;">
-                <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
-            </td>
-            <td>
-              %if r2p.user.username !='default':
-                <span class="delete_icon action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
-                ${_('revoke')}
-                </span>
-              %endif
-            </td>
-            %else:
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none', disabled="disabled")}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read', disabled="disabled")}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write', disabled="disabled")}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")}</td>
-            <td style="white-space: nowrap;">
-                <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
-            </td>
-            <td>
-            </td>
-            %endif
-        </tr>
-    %endfor
-
-    ## USER GROUPS
-    %for g2p in c.repos_group.users_group_to_perm:
-        <tr id="id${id(g2p.users_group.users_group_name)}">
-            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
-            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
-            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
-            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
-            <td style="white-space: nowrap;">
-                <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
-            </td>
-            <td>
-                <span class="delete_icon action_button" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}', '${g2p.users_group.users_group_name}')">
-                ${_('revoke')}
-                </span>
-            </td>
-        </tr>
-    %endfor
-
-    <%
-    _tmpl = h.literal("""' \
-        <td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td><input type="radio" value="group.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td><input type="radio" value="group.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td class="ac"> \
-            <div class="perm_ac" id="perm_ac_{0}"> \
-                <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
-                <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">  \
-                <div id="perm_container_{0}"></div> \
-            </div> \
-        </td> \
-        <td></td>'""")
-    %>
-    ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
-    <tr class="new_members last_new_member" id="add_perm_input"></tr>
-    <tr>
-        <td colspan="6">
-            <span id="add_perm" class="add_icon" style="cursor: pointer;">
-            ${_('Add another member')}
-            </span>
-        </td>
-    </tr>
-    <tr>
-        <td colspan="6">
-           ${h.checkbox('recursive',value="True", label=_('apply to children'))}
-           <span class="help-block">${_('Set or revoke permission to all children of that group, including non-private repositories and other groups')}</span>
-        </td>
-    </tr>
-</table>
-<script type="text/javascript">
-function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
-    url = "${h.url('delete_repo_group_perm_member', group_name=c.repos_group.group_name)}";
-    var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
-    if (confirm(revoke_msg)){
-        ajaxActionRevokePermission(url, obj_id, obj_type, field_id, {recursive:YUD.get('recursive').checked});
-    }
-};
-
-YUE.onDOMReady(function () {
-    if (!YUD.hasClass('perm_new_member_name', 'error')) {
-        YUD.setStyle('add_perm_input', 'display', 'none');
-    }
-    YAHOO.util.Event.addListener('add_perm', 'click', function () {
-        addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
-    });
-});
-
-</script>
--- a/rhodecode/templates/admin/repos_groups/repos_groups.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-<%def name="title()">
-    ${_('%s Group Dashboard') % c.group.group_name} &middot; ${c.rhodecode_name}
-</%def>
-
-<%def name="breadcrumbs()">
-    <span class="groups_breadcrumbs">
-    ${h.link_to(_(u'Home'),h.url('/'))}
-    %if c.group.parent_group:
-        &raquo; ${h.link_to(c.group.parent_group.name,h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
-    %endif
-    &raquo; "${c.group.name}" ${_('with')}
-    </span>
-</%def>
-
-<%def name="page_nav()">
-    ${self.menu('repositories')}
-</%def>
-
-<%def name="main()">
-        <%include file="/index_base.html" args="parent=self,group_name=c.group.group_name"/>
-</%def>
--- a/rhodecode/templates/admin/repos_groups/repos_groups_add.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%def name="title()">
-    ${_('Add repository group')} &middot; ${c.rhodecode_name}
-</%def>
-
-<%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))}
-    &raquo;
-    ${h.link_to(_('Repository groups'),h.url('repos_groups'))}
-    &raquo;
-    ${_('Add new repository group')}
-</%def>
-
-<%def name="page_nav()">
-    ${self.menu('admin')}
-</%def>
-
-<%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    <!-- end box / title -->
-    ${h.form(url('repos_groups'))}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="group_name">${_('Group name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('group_name',class_='medium')}
-                </div>
-             </div>
-
-            <div class="field">
-                <div class="label label-textarea">
-                    <label for="group_description">${_('Description')}:</label>
-                </div>
-                <div class="textarea text-area editor">
-                    ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
-                </div>
-             </div>
-
-             <div class="field">
-                 <div class="label">
-                     <label for="group_parent_id">${_('Group parent')}:</label>
-                 </div>
-                 <div class="input">
-                     ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
-                 </div>
-             </div>
-
-            <div class="buttons">
-              ${h.submit('save',_('save'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-</div>
-</%def>
--- a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%def name="title()">
-    ${_('Edit repository group')} ${c.repos_group.name} &middot; ${c.rhodecode_name}
-</%def>
-
-<%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))}
-    &raquo;
-    ${h.link_to(_('Repository groups'),h.url('repos_groups'))}
-    &raquo;
-    ${_('Edit repository group %s') % c.repos_group.name}
-</%def>
-
-<%def name="page_nav()">
-    ${self.menu('admin')}
-</%def>
-
-<%def name="main()">
-<div class="box box-left">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
-          <li>
-            <span>${h.link_to(_(u'Add child group'),h.url('new_repos_group', parent_group=c.repos_group.group_id))}</span>
-          </li>
-        </ul>
-    </div>
-    <!-- end box / title -->
-    ${h.form(url('repos_group',group_name=c.repos_group.group_name),method='put')}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-            <div class="field">
-                <div class="label">
-                    <label for="group_name">${_('Group name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('group_name',class_='medium')}
-                </div>
-            </div>
-
-            <div class="field">
-                <div class="label label-textarea">
-                    <label for="group_description">${_('Description')}:</label>
-                </div>
-                <div class="textarea text-area editor">
-                    ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
-                </div>
-            </div>
-
-            <div class="field">
-                <div class="label">
-                    <label for="group_parent_id">${_('Group parent')}:</label>
-                </div>
-                <div class="input">
-                    ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
-                </div>
-            </div>
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="enable_locking">${_('Enable locking')}:</label>
-                </div>
-                <div class="checkboxes">
-                    ${h.checkbox('enable_locking',value="True")}
-                    <span class="help-block">${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')}</span>
-                </div>
-            </div>
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-</div>
-<div class="box box-right">
-    <div class="title">
-        <h5>${_('Permissions')}</h5>
-    </div>
-    ${h.form(url('set_repo_group_perm_member', group_name=c.repos_group.group_name),method='post')}
-    <div class="form">
-       <div class="fields">
-            <div class="field">
-                <div class="label">
-                    <label for="input">${_('Permissions')}:</label>
-                </div>
-                <div class="input">
-                    ${h.hidden('repo_private')}
-                    <%include file="repos_group_edit_perms.html"/>
-                </div>
-            </div>
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-            </div>
-       </div>
-    </div>
-    ${h.end_form()}
-</div>
-</%def>
--- a/rhodecode/templates/admin/repos_groups/repos_groups_show.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%def name="title()">
-    ${_('Repository groups administration')} &middot; ${c.rhodecode_name}
-</%def>
-
-
-<%def name="breadcrumbs_links()">
-    %if h.HasPermissionAny('hg.admin')():
-        ${h.link_to(_('Admin'),h.url('admin_home'))}
-    %else:
-        ${_('Admin')}
-    %endif
-    &raquo;
-    ${_('Repository groups')}
-</%def>
-
-<%def name="page_nav()">
-    ${self.menu('admin')}
-</%def>
-
-<%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
-          <li>
-            %if h.HasPermissionAny('hg.admin')():
-             <span>${h.link_to(_(u'Add repository group'),h.url('new_repos_group'))}</span>
-            %endif
-          </li>
-        </ul>
-    </div>
-    <!-- end box / title -->
-    <div class="table">
-           % if c.groups:
-            <table class="table_disp">
-
-                <thead>
-                    <tr>
-                        <th class="left"><a href="#">${_('Group name')}</a></th>
-                        <th class="left"><a href="#">${_('Description')}</a></th>
-                        <th class="left"><a href="#">${_('Number of toplevel repositories')}</a></th>
-                        <th class="left" colspan="2">${_('Action')}</th>
-                    </tr>
-                </thead>
-
-                ## REPO GROUPS
-
-                % for gr in c.groups:
-                    <% gr_cn = gr.repositories.count() %>
-                  <tr>
-                      <td>
-                          <div style="white-space: nowrap">
-                          <img class="icon" alt="${_('Repository group')}" src="${h.url('/images/icons/database_link.png')}"/>
-                          ${h.link_to(h.literal(' &raquo; '.join(map(h.safe_unicode,[g.name for g in gr.parents+[gr]]))), url('repos_group_home',group_name=gr.group_name))}
-                          </div>
-                      </td>
-                      <td>${gr.group_description}</td>
-                      <td><b>${gr_cn}</b></td>
-                      <td>
-                       <a href="${h.url('edit_repos_group',group_name=gr.group_name)}" title="${_('Edit')}">
-                         ${h.submit('edit_%s' % gr.group_name,_('edit'),class_="edit_icon action_button")}
-                       </a>
-                      </td>
-                      <td>
-                       ${h.form(url('repos_group', group_name=gr.group_name),method='delete')}
-                         ${h.submit('remove_%s' % gr.name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_cn) % (gr.name,gr_cn)+"');")}
-                       ${h.end_form()}
-                      </td>
-                  </tr>
-                % endfor
-
-            </table>
-            % else:
-                ${_('There are no repository groups yet')}
-            % endif
-
-    </div>
-</div>
-
-</%def>
--- a/rhodecode/templates/admin/settings/hooks.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%def name="title()">
-    ${_('Settings administration')} &middot; ${c.rhodecode_name}
-</%def>
-
-<%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
-</%def>
-
-<%def name="page_nav()">
-    ${self.menu('admin')}
-</%def>
-
-<%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    <!-- end box / title -->
-
-    <h3>${_('Built in hooks - read only')}</h3>
-    <div class="form">
-        <div class="fields">
-          % for hook in c.hooks:
-            <div class="field">
-                <div class="label label">
-                    <label for="${hook.ui_key}">${hook.ui_key}</label>
-                </div>
-                <div class="input" style="margin-left:280px">
-                  ${h.text(hook.ui_key,hook.ui_value,size=60,readonly="readonly")}
-                </div>
-            </div>
-          % endfor
-        </div>
-    </div>
-    % if c.visual.allow_custom_hooks_settings:
-    <h3>${_('Custom hooks')}</h3>
-    ${h.form(url('admin_setting', setting_id='hooks'),method='put')}
-    <div class="form">
-        <div class="fields">
-
-          % for hook in c.custom_hooks:
-          <div class="field"  id="${'id%s' % hook.ui_id }">
-            <div class="label label">
-                <label for="${hook.ui_key}">${hook.ui_key}</label>
-            </div>
-            <div class="input" style="margin-left:280px">
-                ${h.hidden('hook_ui_key',hook.ui_key)}
-                ${h.hidden('hook_ui_value',hook.ui_value)}
-                ${h.text('hook_ui_value_new',hook.ui_value,size=60)}
-                <span class="delete_icon action_button"
-                onclick="ajaxActionHook(${hook.ui_id},'${'id%s' % hook.ui_id }')">
-                ${_('remove')}
-                </span>
-            </div>
-          </div>
-          % endfor
-
-          <div class="field">
-            <div class="input" style="margin-left:-180px;position: absolute;">
-              <div class="input">
-                 ${h.text('new_hook_ui_key',size=30)}
-              </div>
-            </div>
-            <div class="input" style="margin-left:280px">
-                ${h.text('new_hook_ui_value',size=60)}
-            </div>
-          </div>
-          <div class="buttons" style="margin-left:280px">
-             ${h.submit('save',_('Save'),class_="ui-btn large")}
-          </div>
-        </div>
-    </div>
-    ${h.end_form()}
-    % endif
-</div>
-<script type="text/javascript">
-function ajaxActionHook(hook_id,field_id) {
-    var sUrl = "${h.url('admin_setting', setting_id='hooks')}";
-    var callback = {
-        success: function (o) {
-            var elem = YUD.get(""+field_id);
-            elem.parentNode.removeChild(elem);
-        },
-        failure: function (o) {
-            alert("${_('Failed to remove hook')}");
-        },
-    };
-    var postData = '_method=delete&hook_id=' + hook_id;
-    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
-};
-</script>
-
-</%def>
--- a/rhodecode/templates/admin/settings/settings.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/settings/settings.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Settings administration')} &middot; ${c.rhodecode_name}
+    ${_('Settings administration')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -17,351 +20,39 @@
 
 <%def name="main()">
 <div class="box">
-    <!-- box / title -->
     <div class="title">
         ${self.breadcrumbs()}
     </div>
-    <!-- end box / title -->
-
-    <h3>${_('Remap and rescan repositories')}</h3>
-    ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
-    <div class="form">
-        <!-- fields -->
-
-        <div class="fields">
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="destroy">${_('Rescan option')}:</label>
-                </div>
-                <div class="checkboxes">
-                    <div class="checkbox">
-                        ${h.checkbox('destroy',True)}
-                        <label for="destroy">
-                        <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
-                        ${_('Destroy old data')}</span> </label>
-                    </div>
-                    <div class="checkbox">
-                        ${h.checkbox('invalidate',True)}
-                        <label for="invalidate">
-                        <span class="tooltip" title="${h.tooltip(_('Invalidate cache for all repositories during scan'))}">
-                        ${_('Invalidate cache for all repositories')}</span> </label>
-                    </div>
-                    <span class="help-block">${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')}</span>
-                </div>
-            </div>
-
-            <div class="buttons">
-            ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-
-    <h3>${_('Whoosh indexing')}</h3>
-    ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
-    <div class="form">
-        <!-- fields -->
-
-        <div class="fields">
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Index build option')}:</label>
-                </div>
-                <div class="checkboxes">
-                    <div class="checkbox">
-                        ${h.checkbox('full_index',True)}
-                        <label for="full_index">${_('Build from scratch')}</label>
-                    </div>
-                </div>
-            </div>
-
-            <div class="buttons">
-            ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-
-    <h3>${_('Global application settings')}</h3>
-    ${h.form(url('admin_setting', setting_id='global'),method='put')}
-    <div class="form">
-        <!-- fields -->
-
-        <div class="fields">
-
-             <div class="field">
-                <div class="label">
-                    <label for="rhodecode_title">${_('Site branding')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('rhodecode_title',size=30)}
-                </div>
-             </div>
-
-            <div class="field">
-                <div class="label">
-                    <label for="rhodecode_realm">${_('HTTP authentication realm')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('rhodecode_realm',size=30)}
-                </div>
-            </div>
-
-            <div class="field">
-                <div class="label">
-                    <label for="rhodecode_ga_code">${_('Google Analytics code')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('rhodecode_ga_code',size=30)}
-                </div>
-            </div>
-
-            <div class="buttons">
-                ${h.submit('save',_('Save settings'),class_="ui-btn large")}
-                ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-           </div>
-        </div>
-    </div>
-    ${h.end_form()}
-
-    <h3>${_('Visualisation settings')}</h3>
-    ${h.form(url('admin_setting', setting_id='visual'),method='put')}
-    <div class="form">
-        <!-- fields -->
-
-        <div class="fields">
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('General')}:</label>
-                </div>
-                <div class="checkboxes">
-                    <div class="checkbox">
-                        ${h.checkbox('rhodecode_repository_fields','True')}
-                        <label for="rhodecode_repository_fields">${_('Use repository extra fields')}</label>
-                    </div>
-                    <span class="help-block">${_('Allows storing additional customized fields per repository.')}</span>
-                    <div class="checkbox">
-                        ${h.checkbox('rhodecode_show_version','True')}
-                        <label for="rhodecode_show_version">${_('Show RhodeCode version')}</label>
-                    </div>
-                    <span class="help-block">${_('Shows or hides displayed version of RhodeCode in the footer')}</span>
-                </div>
-             </div>
-            <div class="field">
-                <div class="label">
-                    <label for="rhodecode_realm">${_('Dashboard items')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('rhodecode_dashboard_items',size=5)}
-                    <span class="help-block">${_('Number of items displayed in lightweight dashboard before pagination is shown.')}</span>
-                </div>
-            </div>
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Icons')}:</label>
-                </div>
-                <div class="checkboxes">
-                    <div class="checkbox">
-                        ${h.checkbox('rhodecode_show_public_icon','True')}
-                        <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
-                    </div>
-                    <div class="checkbox">
-                        ${h.checkbox('rhodecode_show_private_icon','True')}
-                        <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
-                    </div>
-                    <span class="help-block">${_('Show public/private icons next to repositories names')}</span>
-                 </div>
-             </div>
 
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Meta-Tagging')}:</label>
-                </div>
-                <div class="checkboxes">
-                    <div class="checkbox">
-                        ${h.checkbox('rhodecode_stylify_metatags','True')}
-                        <label for="rhodecode_stylify_metatags">${_('Stylify recognised metatags:')}</label>
-                    </div>
-                    <div style="padding-left: 20px;">
-                        <ul> <!-- Fix style here -->
-                            <li>[featured] <span class="metatag" tag="featured">featured</span></li>
-                            <li>[stale] <span class="metatag" tag="stale">stale</span></li>
-                            <li>[dead] <span class="metatag" tag="dead">dead</span></li>
-                            <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
-                            <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
-                            <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
-                            <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
-                            <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
-                        </ul>
-                    </div>
-                 </div>
-             </div>
-
-             <div class="buttons">
-                 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
-                 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-             </div>
-
-        </div>
-    </div>
-    ${h.end_form()}
-
-
-    <h3>${_('VCS settings')}</h3>
-    ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
-    <div class="form">
-        <!-- fields -->
-
-        <div class="fields">
-
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Web')}:</label>
-                </div>
-                <div class="checkboxes">
-                    <div class="checkbox">
-                        ${h.checkbox('web_push_ssl', 'True')}
-                        <label for="web_push_ssl">${_('Require SSL for vcs operations')}</label>
-                    </div>
-                    <span class="help-block">${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}</span>
-                </div>
-             </div>
+    ##main
+    <div style="width: 150px; float:left">
+        <ul class="nav nav-pills nav-stacked">
+          <li>
+           <div class="gravatar_box" style="height: 26px">
+               <div class="gravatar" style="float: left">
+                <i class="icon-cog" style="font-size: 26px"></i>
+               </div>
+               <div style="margin:10px 0px 10px 0px; color:#5f5f5f; float:left">
+                <strong>${_('Settings')}</strong>
+               </div>
+           </div>
+          </li>
+          <li class="${'active' if c.active=='vcs' else ''}"><a href="${h.url('admin_settings')}">${_('VCS')}</a></li>
+          <li class="${'active' if c.active=='mapping' else ''}"><a href="${h.url('admin_settings_mapping')}">${_('Remap and rescan')}</a></li>
+          <li class="${'active' if c.active=='global' else ''}"><a href="${h.url('admin_settings_global')}">${_('Global')}</a></li>
+          <li class="${'active' if c.active=='visual' else ''}"><a href="${h.url('admin_settings_visual')}">${_('Visual')}</a></li>
+          <li class="${'active' if c.active=='email' else ''}"><a href="${h.url('admin_settings_email')}">${_('Email')}</a></li>
+          <li class="${'active' if c.active=='hooks' else ''}"><a href="${h.url('admin_settings_hooks')}">${_('Hooks')}</a></li>
+          <li class="${'active' if c.active=='search' else ''}"><a href="${h.url('admin_settings_search')}">${_('Full text search')}</a></li>
+          <li class="${'active' if c.active=='system' else ''}"><a href="${h.url('admin_settings_system')}">${_('System Info')}</a></li>
+          <li class="${'active' if c.active=='license' else ''}"><a href="${h.url('admin_settings_license')}">${_('License')}</a></li>
 
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Hooks')}:</label>
-                </div>
-                <div class="checkboxes">
-                    <div class="checkbox">
-                        ${h.checkbox('hooks_changegroup_update','True')}
-                        <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
-                    </div>
-                    <div class="checkbox">
-                        ${h.checkbox('hooks_changegroup_repo_size','True')}
-                        <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
-                    </div>
-                    <div class="checkbox">
-                        ${h.checkbox('hooks_changegroup_push_logger','True')}
-                        <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
-                    </div>
-                    <div class="checkbox">
-                        ${h.checkbox('hooks_outgoing_pull_logger','True')}
-                        <label for="hooks_outgoing_pull_logger">${_('Log user pull commands')}</label>
-                    </div>
-                </div>
-                <div class="input" style="margin-top:10px">
-                    ${h.link_to(_('Advanced setup'),url('admin_edit_setting',setting_id='hooks'))}
-                </div>
-             </div>
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Mercurial Extensions')}:</label>
-                </div>
-                <div class="checkboxes">
-                    <div class="checkbox">
-                        ${h.checkbox('extensions_largefiles','True')}
-                        <label for="extensions_largefiles">${_('Enable largefiles extension')}</label>
-                    </div>
-                    <div class="checkbox">
-                        ${h.checkbox('extensions_hgsubversion','True')}
-                        <label for="extensions_hgsubversion">${_('Enable hgsubversion extension')}</label>
-                    </div>
-                    <span class="help-block">${_('Requires hgsubversion library installed. Allows cloning from svn remote locations')}</span>
-                    ##<div class="checkbox">
-                    ##    ${h.checkbox('extensions_hggit','True')}
-                    ##    <label for="extensions_hggit">${_('Enable hg-git extension')}</label>
-                    ##</div>
-                    ##<span class="help-block">${_('Requires hg-git library installed. Allows cloning from git remote locations')}</span>
-                </div>
-            </div>
-            %if c.visual.allow_repo_location_change:
-            <div class="field">
-                <div class="label">
-                    <label for="paths_root_path">${_('Repositories location')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('paths_root_path',size=30,readonly="readonly", class_="disabled")}
-                    <span id="path_unlock" class="tooltip" style="cursor: pointer"
-                            title="${h.tooltip(_('Click to unlock. You must restart RhodeCode in order to make this setting take effect.'))}">
-                        ${_('Unlock')}
-                    </span>
-                    <span class="help-block">${_('Location where repositories are stored. After changing this value a restart, and rescan is required')}</span>
-                </div>
-            </div>
-            %else:
-            ## form still requires this but we cannot internally change it anyway
-            ${h.hidden('paths_root_path',size=30,readonly="readonly", class_="disabled")}
-            %endif
-            <div class="buttons">
-                ${h.submit('save',_('Save settings'),class_="ui-btn large")}
-                ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-           </div>
-        </div>
-    </div>
-    ${h.end_form()}
-
-    <script type="text/javascript">
-        YAHOO.util.Event.onDOMReady(function(){
-            YUE.on('path_unlock','click',function(){
-                YUD.get('paths_root_path').removeAttribute('readonly');
-                YUD.removeClass('paths_root_path', 'disabled')
-            });
-        });
-    </script>
-
-    <h3>${_('Test Email')}</h3>
-    ${h.form(url('admin_setting', setting_id='email'),method='put')}
-    <div class="form">
-        <!-- fields -->
-
-        <div class="fields">
-            <div class="field">
-                <div class="label">
-                    <label for="test_email">${_('Email to')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('test_email',size=30)}
-                </div>
-            </div>
-
-            <div class="buttons">
-            ${h.submit('send',_('Send'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-
-    <h3>${_('System Info and Packages')}</h3>
-    <div class="form">
-      <div>
-        <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('Show')} &darr;</h5>
-      </div>
-      <div id="expand_modules_table"  style="display:none">
-      <h5>Python - ${c.py_version}</h5>
-      <h5>System - ${c.platform}</h5>
-
-      <table class="table" style="margin:0px 0px 0px 20px">
-          <colgroup>
-              <col style="width:220px">
-          </colgroup>
-          <tbody>
-              %for key, value in c.modules:
-                  <tr>
-                      <th style="text-align: right;padding-right:5px;">${key}</th>
-                      <td>${value}</td>
-                  </tr>
-              %endfor
-          </tbody>
-      </table>
-      </div>
+        </ul>
     </div>
 
-    <script type="text/javascript">
-    YUE.on('expand_modules','click',function(e){
-            YUD.setStyle('expand_modules_table','display','');
-            YUD.setStyle('expand_modules','display','none');
-    })
-    </script>
+    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
+        <%include file="/admin/settings/settings_${c.active}.html"/>
+    </div>
+</div>
 
-</div>
 </%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/settings/settings_email.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,43 @@
+<dl class="dl-horizontal">
+<%
+ elems = [
+    (_('Email prefix'), c.rhodecode_ini.get('email_prefix'), ''),
+    (_('RhodeCode email from'), c.rhodecode_ini.get('app_email_from'), ''),
+    (_('Error email from'), c.rhodecode_ini.get('error_email_from'), ''),
+    (_('Error email recipients'), c.rhodecode_ini.get('email_to'), ''),
+
+    (_('SMTP server'), c.rhodecode_ini.get('smtp_server'), ''),
+    (_('SMTP username'), c.rhodecode_ini.get('smtp_username'), ''),
+    (_('SMTP password'), '%s chars' % len(c.rhodecode_ini.get('smtp_password', '')), ''),
+    (_('SMTP port'), c.rhodecode_ini.get('smtp_port'), ''),
+
+    (_('SMTP use TLS'), c.rhodecode_ini.get('smtp_use_tls'), ''),
+    (_('SMTP use SSL'), c.rhodecode_ini.get('smtp_use_ssl'), ''),
+    (_('SMTP auth'), c.rhodecode_ini.get('smtp_auth'), ''),
+ ]
+%>
+%for dt, dd, tt in elems:
+  <dt style="width:150px; text-align: left">${dt}:</dt>
+  <dd style="margin-left: 160px" title="${tt}">${dd}</dd>
+%endfor
+</dl>
+
+${h.form(url('admin_settings_email'), method='post')}
+    <div class="form">
+
+        <div class="fields">
+            <div class="field">
+                <div class="label">
+                    <label for="test_email">${_('Send test email to')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('test_email',size=30)}
+                </div>
+            </div>
+
+            <div class="buttons">
+            ${h.submit('send',_('Send'),class_="btn")}
+            </div>
+        </div>
+    </div>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/settings/settings_global.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,60 @@
+${h.form(url('admin_settings_global'), method='post')}
+    <div class="form">
+
+        <div class="fields">
+
+             <div class="field">
+                <div class="label">
+                    <label for="rhodecode_title">${_('Site branding')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('rhodecode_title',size=30)}
+                    <span class="help-block">${_('Set a custom title for your RhodeCode Service.')}</span>
+                </div>
+             </div>
+
+            <div class="field">
+                <div class="label">
+                    <label for="rhodecode_realm">${_('HTTP authentication realm')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('rhodecode_realm',size=30)}
+                </div>
+            </div>
+
+            <div class="field">
+                <div class="label">
+                    <label for="rhodecode_ga_code">${_('Google Analytics code')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('rhodecode_ga_code',size=30)}
+                </div>
+            </div>
+
+            <div class="field">
+                <div class="label">
+                    <label for="rhodecode_captcha_public_key">${_('ReCaptcha public key')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('rhodecode_captcha_public_key',size=60)}
+                    <span class="help-block">${_('Public key for reCaptcha system.')}</span>
+                </div>
+            </div>
+
+            <div class="field">
+                <div class="label">
+                    <label for="rhodecode_captcha_private_key">${_('ReCaptcha private key')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('rhodecode_captcha_private_key',size=60)}
+                    <span class="help-block">${_('Private key for reCaptcha system. Setting this value will enable captcha on registration')}</span>
+                </div>
+            </div>
+
+            <div class="buttons">
+                ${h.submit('save',_('Save settings'),class_="btn")}
+                ${h.reset('reset',_('Reset'),class_="btn")}
+           </div>
+        </div>
+    </div>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/settings/settings_hooks.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,75 @@
+<h4>${_('Built in Mercurial hooks - read only')}</h4>
+<div class="form">
+    <div class="fields">
+      % for hook in c.hooks:
+        <div class="field">
+            <div class="label label">
+                <label for="${hook.ui_key}">${hook.ui_key}</label>
+            </div>
+            <div class="input" style="margin-left:280px">
+              ${h.text(hook.ui_key,hook.ui_value,size=60,readonly="readonly")}
+            </div>
+        </div>
+      % endfor
+    </div>
+    <span class="help-block">${_('Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications.')}</span>
+</div>
+
+% if c.visual.allow_custom_hooks_settings:
+<h4>${_('Custom hooks')}</h4>
+${h.form(url('admin_settings_hooks'), method='post')}
+<div class="form">
+    <div class="fields">
+
+      % for hook in c.custom_hooks:
+      <div class="field"  id="${'id%s' % hook.ui_id }">
+        <div class="label label">
+            <label for="${hook.ui_key}">${hook.ui_key}</label>
+        </div>
+        <div class="input" style="margin-left:280px">
+            ${h.hidden('hook_ui_key',hook.ui_key)}
+            ${h.hidden('hook_ui_value',hook.ui_value)}
+            ${h.text('hook_ui_value_new',hook.ui_value,size=60)}
+            <span class="action_button"
+            onclick="ajaxActionHook(${hook.ui_id},'${'id%s' % hook.ui_id }')">
+            <i class="icon-remove-sign" style="color:#FF4444"></i>
+            ${_('delete')}
+            </span>
+        </div>
+      </div>
+      % endfor
+
+      <div class="field">
+        <div class="input" style="margin-left:-135px;position: absolute;">
+          <div class="input">
+             ${h.text('new_hook_ui_key',size=30)}
+          </div>
+        </div>
+        <div class="input" style="margin-left:280px">
+            ${h.text('new_hook_ui_value',size=60)}
+        </div>
+      </div>
+      <div class="buttons" style="margin-left:280px">
+         ${h.submit('save',_('Save'),class_="btn")}
+      </div>
+    </div>
+</div>
+${h.end_form()}
+% endif
+
+<script type="text/javascript">
+function ajaxActionHook(hook_id,field_id) {
+    var sUrl = "${h.url('admin_settings_hooks')}";
+    var callback = {
+        success: function (o) {
+            var elem = YUD.get(""+field_id);
+            elem.parentNode.removeChild(elem);
+        },
+        failure: function (o) {
+            alert("${_('Failed to remove hook')}");
+        },
+    };
+    var postData = '_method=delete&hook_id=' + hook_id;
+    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/settings/settings_license.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,59 @@
+%if not c.license_loaded:
+<strong>${_("Currently you're using a free license, which is limited to 20 users.")}</strong>
+<br/><br/>
+%endif
+
+<dl class="dl-horizontal">
+<%
+ elems = [
+    (_('RhodeCode version'), c.rhodecode_version, ''),
+    (_('License token'), h.literal('<pre>%s</pre>' % c.rhodecode_ini.get('license_token', _('No license token'))), ''),
+ ]
+ if c.license_info:
+    elems.append((_('License issued to'), '%s %s (%s)' % (c.license_info.get('first_name'),c.license_info.get('last_name'),c.license_info.get('company')), ''))
+    elems.append((_('License issued on'), h.fmt_date(h.time_to_datetime(c.license_info.get('issue_date'))), ''))
+    elems.append((_('License users limit'), c.license_info.get('users') if c.license_info.get('users') != -1 else _('unlimited'), ''))
+    elems.append((_('License expires on'), h.fmt_date(h.time_to_datetime(c.license_info.get('valid_till'))), ''))
+%>
+%for dt, dd, tt in elems:
+  <dt style="width:150px; text-align: left">${dt}:</dt>
+  <dd style="margin-left: 160px" title="${tt}">${dd}</dd>
+%endfor
+</dl>
+
+
+%if c.license_token:
+${h.form(url('admin_settings_license'), method='post')}
+    <div class="form">
+
+        <div class="fields">
+
+            <div class="field">
+                <div class="label label-textarea">
+                    <label for="rhodecode_license_key">${_('License key')}:</label>
+                </div>
+                <div class="textarea text-area editor">
+                    ${h.textarea('rhodecode_license_key', style="height:165px")}
+                    <span class="help-block">${_('Paste your RhodeCode license key into that textarea.')}</span>
+                </div>
+            </div>
+
+            <div class="buttons">
+                ${h.submit('save',_('Save settings'),class_="btn")}
+                ${h.reset('reset',_('Reset'),class_="btn")}
+           </div>
+        </div>
+    </div>
+${h.end_form()}
+
+
+%else:
+<div>
+${_('Please enter following lines (if yet not present) into [app:main] section of your .ini file. The token below is autogenerated.')}
+<pre>
+
+<%text>## license token</%text>
+license_token = ${c.generated_license_token}
+</pre>
+</div>
+%endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/settings/settings_mapping.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,36 @@
+${h.form(url('admin_settings_mapping'), method='post')}
+    <div class="form">
+        <div class="fields">
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="destroy">${_('Rescan option')}:</label>
+                </div>
+                <div class="checkboxes">
+                    <div class="checkbox">
+                        ${h.checkbox('destroy',True)}
+                        <label for="destroy">${_('Destroy old data')}</label>
+                    </div>
+                    <span class="help-block">${_('In case a repository was deleted from filesystem and it still exists in the database check this option to scan obsolete data in database and remove it.')}</span>
+
+                    <div class="checkbox">
+                        ${h.checkbox('invalidate',True)}
+                        <label for="invalidate"> ${_('Invalidate cache for all repositories')}</label>
+                    </div>
+                    <span class="help-block">${_('Each cache data for repositories will be cleaned with this option selected. Use this to reload data and clear cache keys.')}</span>
+
+                    <div class="checkbox">
+                        ${h.checkbox('hooks',True)}
+                        <label for="hooks"> ${_('Install GIT hooks')} </label>
+                    </div>
+                    <span class="help-block">${_('Verify if RhodeCodes GIT hooks are installed for each repository. Current hooks will be updated to latest version')}</span>
+                </div>
+
+                </div>
+            </div>
+
+            <div class="buttons">
+            ${h.submit('rescan',_('Rescan Repositories'),class_="btn")}
+            </div>
+        </div>
+    </div>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/settings/settings_search.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,25 @@
+${h.form(url('admin_settings_search'), method='post')}
+    <div class="form">
+
+        <div class="fields">
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label>${_('Index build option')}:</label>
+                </div>
+                <div class="checkboxes">
+                    <div class="checkbox">
+                        ${h.checkbox('full_index',True)}
+                        <label for="full_index">${_('Build from scratch')}</label>
+
+                    </div>
+                    <span class="help-block">${_('This option completely reindex all the files within RhodeCode for proper fulltext search capabilities.')}</span>
+
+                </div>
+            </div>
+
+            <div class="buttons">
+            ${h.submit('reindex',_('Reindex'),class_="btn")}
+            </div>
+        </div>
+    </div>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/settings/settings_system.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,43 @@
+<dl class="dl-horizontal">
+<%
+ elems = [
+    (_('RhodeCode version'), h.literal('%s <b><span style="color:#036185; text-decoration: underline;cursor: pointer" id="check_for_update" >%s</span></b>' % (c.rhodecode_version, _('check for updates'))), ''),
+    (_('Python version'), c.py_version, ''),
+    (_('Platform'), c.platform, ''),
+    (_('GIT version'), c.git_version, ''),
+    (_('GIT path'), c.rhodecode_ini.get('git_path'), ''),
+    (_('Upgrade info endpoint'), h.literal('%s <br/><span style="color:#999999">%s.</span>' % (c.rhodecode_update_url, _('Note: please make sure this server can access this url'))), ''),
+ ]
+%>
+
+<div id="update_notice" style="display: none">
+    <div style="padding: 5px 0px 5px 0px; color: #000000; font-weight: bold">${_('Checking for updates...')}</div>
+</div>
+%for dt, dd, tt in elems:
+  <dt style="width:150px; text-align: left">${dt}:</dt>
+  <dd style="margin-left: 160px" title="${tt}">${dd}</dd>
+%endfor
+</dl>
+
+<h4>${_('Python packages')}</h4>
+<table class="table" style="margin:0px 0px 0px 0px">
+  <colgroup>
+      <col style="width:180px">
+  </colgroup>
+  <tbody>
+      %for key, value in c.modules:
+          <tr>
+              <td style="padding-right:5px;">${key}</td>
+              <td>${value}</td>
+          </tr>
+      %endfor
+  </tbody>
+</table>
+
+<script>
+    $('#check_for_update').click(function(e){
+        $('#update_notice').show();
+        ypjax("${h.url('admin_settings_system_update')}",
+              "update_notice", function(){});
+    })
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/settings/settings_system_update.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,27 @@
+## -*- coding: utf-8 -*-
+## upgrade block rendered afte on-click check
+
+<div class="alert ${'alert-warning' if c.should_upgrade else 'alert-success'}">
+<p style="padding: 2px 0px 5px 0px; margin: 0px">
+
+%if c.should_upgrade:
+    A <b>new version</b> is available:
+    %if c.latest_data.get('title'):
+        <b>${h.literal(c.latest_data['title'])}</b>
+    %else:
+        <b>${c.latest_ver}</b>
+    %endif
+%else:
+    You already have the <b>latest</b> stable version.
+%endif
+</p>
+
+% if c.should_upgrade and c.important_notices:
+<div style="color: #5f5f5f; padding: 3px 0px 5px 0px;">Important notes for this release:</div>
+    <ul>
+    % for notice in c.important_notices:
+        <li>- ${notice}</li>
+    % endfor
+    </ul>
+% endif
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/settings/settings_vcs.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,96 @@
+${h.form(url('admin_settings'), method='post')}
+    <div class="form">
+        <div class="fields">
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label>${_('Web')}:</label>
+                </div>
+                <div class="checkboxes">
+                    <div class="checkbox">
+                        ${h.checkbox('web_push_ssl', 'True')}
+                        <label for="web_push_ssl">${_('Require SSL for vcs operations')}</label>
+                    </div>
+                    <span class="help-block">${_('Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable.')}</span>
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label label-checkbox">
+                    <label>${_('Hooks')}:</label>
+                </div>
+                <div class="checkboxes">
+                    <div class="checkbox">
+                        ${h.checkbox('hooks_changegroup_repo_size','True')}
+                        <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
+                    </div>
+                    <div class="checkbox">
+                        ${h.checkbox('hooks_changegroup_push_logger','True')}
+                        <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
+                    </div>
+                    <div class="checkbox">
+                        ${h.checkbox('hooks_outgoing_pull_logger','True')}
+                        <label for="hooks_outgoing_pull_logger">${_('Log user pull commands')}</label>
+                    </div>
+                    <div class="checkbox">
+                        ${h.checkbox('hooks_changegroup_update','True')}
+                        <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
+                    </div>
+                </div>
+             </div>
+             <div class="field">
+                <div class="label label-checkbox">
+                    <label>${_('Mercurial Extensions')}:</label>
+                </div>
+                <div class="checkboxes">
+                    <div class="checkbox">
+                        ${h.checkbox('extensions_largefiles','True')}
+                        <label for="extensions_largefiles">${_('Enable largefiles extension')}</label>
+                    </div>
+                    <div class="checkbox">
+                        ${h.checkbox('extensions_hgsubversion','True')}
+                        <label for="extensions_hgsubversion">${_('Enable hgsubversion extension')}</label>
+                    </div>
+                    <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
+                    ##<div class="checkbox">
+                    ##    ${h.checkbox('extensions_hggit','True')}
+                    ##    <label for="extensions_hggit">${_('Enable hg-git extension')}</label>
+                    ##</div>
+                    ##<span class="help-block">${_('Requires hg-git library to be installed. Allows cloning remote git repositories and migrates them to Mercurial type.')}</span>
+                </div>
+            </div>
+            %if c.visual.allow_repo_location_change:
+            <div class="field">
+                <div class="label">
+                    <label for="paths_root_path">${_('Repositories location')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('paths_root_path',size=60,readonly="readonly", class_="disabled")}
+                    <span id="path_unlock" class="tooltip" style="cursor: pointer"
+                            title="${h.tooltip(_('Click to unlock. You must restart RhodeCode in order to make this setting take effect.'))}">
+                        <div class="btn btn-small"><i id="path_unlock_icon" class="icon-lock"></i></div>
+                    </span>
+                    <span class="help-block">${_('Filesystem location where repositories should be stored. After changing this value a restart and rescan of the repository folder are required.')}</span>
+                </div>
+            </div>
+            %else:
+            ## form still requires this but we cannot internally change it anyway
+            ${h.hidden('paths_root_path',size=30,readonly="readonly", class_="disabled")}
+            %endif
+            <div class="buttons">
+                ${h.submit('save',_('Save settings'),class_="btn")}
+                ${h.reset('reset',_('Reset'),class_="btn")}
+           </div>
+        </div>
+    </div>
+    ${h.end_form()}
+
+    <script type="text/javascript">
+        $(document).ready(function(){
+            $('#path_unlock').on('click', function(e){
+                $('#path_unlock_icon').removeClass('icon-lock');
+                $('#path_unlock_icon').addClass('icon-unlock');
+                $('#paths_root_path').removeAttr('readonly');
+                $('#paths_root_path').removeClass('disabled');
+            })
+        })
+    </script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/settings/settings_visual.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,121 @@
+${h.form(url('admin_settings_visual'), method='post')}
+    <div class="form">
+
+        <div class="fields">
+
+             <div class="field">
+                <div class="label label-checkbox">
+                    <label>${_('General')}:</label>
+                </div>
+                <div class="checkboxes">
+                    <div class="checkbox">
+                        ${h.checkbox('rhodecode_repository_fields','True')}
+                        <label for="rhodecode_repository_fields">${_('Use repository extra fields')}</label>
+                    </div>
+                    <span class="help-block">${_('Allows storing additional customized fields per repository.')}</span>
+                    <div class="checkbox">
+                        ${h.checkbox('rhodecode_show_version','True')}
+                        <label for="rhodecode_show_version">${_('Show RhodeCode version')}</label>
+                    </div>
+                    <span class="help-block">${_('Shows or hides a version number of RhodeCode displayed in the footer.')}</span>
+
+                    <div class="checkbox">
+                        ${h.checkbox('rhodecode_use_gravatar','True')}
+                        <label for="rhodecode_use_gravatar">${_('Use Gravatars in RhodeCode')}</label>
+                    </div>
+                </div>
+                <div class="field">
+                    <div class="input">
+                        ${h.text('rhodecode_gravatar_url', size=80)}
+                        <span class="help-block">${_('''Gravatar url allows you to use other avatar server application.
+                                                        Following variables of the URL will be replaced accordingly.
+                                                        {scheme}    'http' or 'https' sent from running RhodeCode server,
+                                                        {email}     user email,
+                                                        {md5email}  md5 hash of the user email (like at gravatar.com),
+                                                        {size}      size of the image that is expected from the server application,
+                                                        {netloc}    network location/server host of running RhodeCode server''')}</span>
+                    </div>
+                </div>
+                <div class="field">
+                    <div class="input">
+                        ${h.text('rhodecode_clone_uri_tmpl', size=80)}
+                        <span class="help-block">${_('''Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars:
+                                                        {scheme} 'http' or 'https' sent from running RhodeCode server,
+                                                        {user}   current user username,
+                                                        {netloc} network location/server host of running RhodeCode server,
+                                                        {repo}   full repository name,
+                                                        {repoid} ID of repository, can be used to contruct clone-by-id''')}</span>
+                    </div>
+                </div>
+             </div>
+
+            <div class="field">
+                <div class="label">
+                    <label for="rhodecode_dashboard_items">${_('Dashboard items')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('rhodecode_dashboard_items',size=5)}
+                    <span class="help-block">${_('Number of items displayed in the main page dashboard before pagination is shown.')}</span>
+                </div>
+            </div>
+
+            <div class="field">
+                <div class="label">
+                    <label for="rhodecode_admin_grid_items">${_('Admin pages items')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('rhodecode_admin_grid_items',size=5)}
+                    <span class="help-block">${_('Number of items displayed in the admin pages grids before pagination is shown.')}</span>
+                </div>
+            </div>
+
+             <div class="field">
+                <div class="label label-checkbox">
+                    <label>${_('Icons')}:</label>
+                </div>
+                <div class="checkboxes">
+                    <div class="checkbox">
+                        ${h.checkbox('rhodecode_show_public_icon','True')}
+                        <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
+                    </div>
+                    <div class="checkbox">
+                        ${h.checkbox('rhodecode_show_private_icon','True')}
+                        <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
+                    </div>
+                    <span class="help-block">${_('Show public/private icons next to repositories names.')}</span>
+                 </div>
+             </div>
+
+             <div class="field">
+                <div class="label label-checkbox">
+                    <label>${_('Meta-Tagging')}:</label>
+                </div>
+                <div class="checkboxes">
+                    <div class="checkbox">
+                        ${h.checkbox('rhodecode_stylify_metatags','True')}
+                        <label for="rhodecode_stylify_metatags">${_('Stylify recognised meta tags:')}</label>
+                    </div>
+                    <div style="padding-left: 20px;">
+                        <ul> <!-- Fix style here -->
+                            <li>[featured] <span class="metatag" tag="featured">featured</span></li>
+                            <li>[stale] <span class="metatag" tag="stale">stale</span></li>
+                            <li>[dead] <span class="metatag" tag="dead">dead</span></li>
+                            <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
+                            <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
+                            <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
+                            <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
+                            <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
+                        </ul>
+                    </div>
+                    <span class="help-block">${_('Parses meta tags from repository description field and turns them into colored tags.')}</span>
+                 </div>
+             </div>
+
+             <div class="buttons">
+                 ${h.submit('save',_('Save settings'),class_="btn")}
+                 ${h.reset('reset',_('Reset'),class_="btn")}
+             </div>
+
+        </div>
+    </div>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/user_groups/user_group_add.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,72 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('Add user group')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+<%def name="breadcrumbs_links()">
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${h.link_to(_('User groups'),h.url('users_groups'))}
+    &raquo;
+    ${_('Add User Group')}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('admin')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+    <!-- end box / title -->
+    ${h.form(url('users_groups'))}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+             <div class="field">
+                <div class="label">
+                    <label for="users_group_name">${_('Group name')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('users_group_name',class_='small')}
+                </div>
+             </div>
+            <div class="field">
+                <div class="label label-textarea">
+                    <label for="user_group_description">${_('Description')}:</label>
+                </div>
+                <div class="textarea text-area editor">
+                    ${h.textarea('user_group_description')}
+                    <span class="help-block">${_('Short, optional description for this user group.')}</span>
+                </div>
+             </div>
+             <div class="field">
+                <div class="label label-checkbox">
+                    <label for="users_group_active">${_('Active')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('users_group_active',value=True, checked='checked')}
+                </div>
+             </div>
+
+            <div class="buttons">
+              ${h.submit('save',_('Save'),class_="btn")}
+            </div>
+        </div>
+    </div>
+    ${h.end_form()}
+</div>
+</%def>
+
+<script>
+    $(document).ready(function(){
+        $('#users_group_name').focus();
+    })
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/user_groups/user_group_edit.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,54 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('%s user group settings') % c.user_group.users_group_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${h.link_to(_('User Groups'),h.url('users_groups'))}
+    &raquo;
+    ${c.user_group.users_group_name}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('admin')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+
+    ##main
+    <div style="width: 150px; float:left">
+        <ul class="nav nav-pills nav-stacked">
+          <li>
+           <div class="gravatar_box" style="height: 26px">
+             <div class="gravatar" style="float: left">
+                <i class="icon-group" style="font-size: 26px"></i>
+             </div>
+               <div class="truncate" style="margin:7px 0px 10px 0px; color:#5f5f5f; float:left; width: 100px">
+                <strong>${c.user_group.users_group_name}</strong>
+               </div>
+           </div>
+          </li>
+          <li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_users_group', id=c.user_group.users_group_id)}">${_('Settings')}</a></li>
+          <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_group_advanced', id=c.user_group.users_group_id)}">${_('Advanced')}</a></li>
+          <li class="${'active' if c.active=='default_perms' else ''}"><a href="${h.url('edit_user_group_default_perms', id=c.user_group.users_group_id)}">${_('Default permissions')}</a></li>
+          <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_group_perms', id=c.user_group.users_group_id)}">${_('Permissions')}</a></li>
+          <li class="${'active' if c.active=='members' else ''}"><a href="${h.url('edit_user_group_members', id=c.user_group.users_group_id)}">${_('Members')}</a></li>
+        </ul>
+    </div>
+
+    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
+        <%include file="/admin/user_groups/user_group_edit_${c.active}.html"/>
+    </div>
+</div>
+</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/user_groups/user_group_edit_advanced.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,23 @@
+<div style="font-size: 24px; color: #666666; padding: 0px 0px 10px 0px">${_('User Group: %s') % c.user_group.users_group_name}</div>
+
+<dl class="dl-horizontal">
+<%
+ elems = [
+    (_('Members'), len(c.group_members_obj), ''),
+    (_('Created on'), h.fmt_date(c.user_group.created_on), ''),
+    (_('Owner'), h.person(c.user_group.user.username), ''),
+ ]
+%>
+%for dt, dd, tt in elems:
+  <dt style="width:150px; text-align: left">${dt}:</dt>
+  <dd style="margin-left: 160px" title="${tt}">${dd}</dd>
+%endfor
+</dl>
+
+${h.form(h.url('users_group', id=c.user_group.users_group_id),method='delete')}
+    <button class="btn btn-small btn-danger" type="submit"
+            onclick="return confirm('${_('Confirm to delete this user group: %s') % c.user_group.users_group_name}');">
+        <i class="icon-remove-sign"></i>
+        ${_('Delete this user group')}
+    </button>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/user_groups/user_group_edit_default_perms.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,6 @@
+<%namespace name="dpb" file="/base/default_perms_box.html"/>
+${dpb.default_perms_box(url('edit_user_group_default_perms', id=c.user_group.users_group_id))}
+
+## permissions overview
+<%namespace name="p" file="/base/perms_summary.html"/>
+${p.perms_summary(c.permissions)}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/user_groups/user_group_edit_members.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,17 @@
+<div class="group_members_wrap">
+% if c.group_members_obj:
+  <ul class="group_members">
+  %for user in c.group_members_obj:
+    <li>
+      <div class="group_member">
+        <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div>
+        <div>${h.link_to(user.username, h.url('edit_user',id=user.user_id))}</div>
+        <div>${user.full_name}</div>
+      </div>
+    </li>
+  %endfor
+  </ul>
+  %else:
+    <span class="empty_data">${_('No members yet')}</span>
+  %endif
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/user_groups/user_group_edit_perms.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,128 @@
+${h.form(url('edit_user_group_perms', id=c.user_group.users_group_id),method='put')}
+<div class="form">
+   <div class="fields">
+        <div class="field">
+            <table id="permissions_manage" class="noborder">
+                <tr>
+                    <td>${_('none')}</td>
+                    <td>${_('read')}</td>
+                    <td>${_('write')}</td>
+                    <td>${_('admin')}</td>
+                    <td>${_('user/user group')}</td>
+                    <td></td>
+                </tr>
+                ## USERS
+                %for r2p in c.user_group.user_user_group_to_perm:
+                    ##forbid revoking permission from yourself, except if you're an super admin
+                    <tr id="id${id(r2p.user.username)}">
+                        %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin:
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none')}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read')}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write')}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin')}</td>
+                        <td style="white-space: nowrap;">
+                            <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>
+                            %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default':
+                             <a href="${h.url('edit_user',id=r2p.user.user_id)}">${r2p.user.username}</a>
+                            %else:
+                             ${r2p.user.username if r2p.user.username != 'default' else _('default')}
+                            %endif
+                        </td>
+                        <td>
+                          %if r2p.user.username !='default':
+                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
+                             <i class="icon-remove"></i> ${_('revoke')}
+                            </span>
+                          %endif
+                        </td>
+                        %else:
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none', disabled="disabled")}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read', disabled="disabled")}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write', disabled="disabled")}</td>
+                        <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin', disabled="disabled")}</td>
+                        <td style="white-space: nowrap;">
+                            <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>
+                            ${r2p.user.username if r2p.user.username != 'default' else _('default')}
+                        </td>
+                        <td><i class="icon-user"></i> ${_('delegated admin')}</td>
+                        %endif
+                    </tr>
+                %endfor
+
+                ## USER GROUPS
+                %for g2p in c.user_group.user_group_user_group_to_perm:
+                    <tr id="id${id(g2p.user_group.users_group_name)}">
+                        <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.none')}</td>
+                        <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.read')}</td>
+                        <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.write')}</td>
+                        <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.admin')}</td>
+                        <td style="white-space: nowrap;">
+                            <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
+                            %if h.HasPermissionAny('hg.admin')():
+                             <a href="${h.url('edit_users_group',id=g2p.user_group.users_group_id)}">
+                                 ${g2p.user_group.users_group_name}
+                             </a>
+                            %else:
+                             ${g2p.user_group.users_group_name}
+                            %endif
+                        </td>
+                        <td>
+                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${g2p.user_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.user_group.users_group_name)}', '${g2p.user_group.users_group_name}')">
+                            <i class="icon-remove"></i> ${_('revoke')}
+                            </span>
+                        </td>
+                    </tr>
+                %endfor
+
+                <%
+                _tmpl = h.literal("""' \
+                    <td><input type="radio" value="usergroup.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td><input type="radio" value="usergroup.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td><input type="radio" value="usergroup.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td><input type="radio" value="usergroup.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
+                    <td class="ac"> \
+                        <div class="perm_ac" id="perm_ac_{0}"> \
+                            <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
+                            <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">  \
+                            <div id="perm_container_{0}"></div> \
+                        </div> \
+                    </td> \
+                    <td></td>'""")
+                %>
+                ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
+                <tr class="new_members last_new_member" id="add_perm_input"></tr>
+                <tr>
+                    <td colspan="6">
+                        <span id="add_perm" style="cursor: pointer;">
+                            <i class="icon-plus"></i> ${_('Add new')}
+                        </span>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div class="buttons">
+          ${h.submit('save',_('Save'),class_="btn")}
+          ${h.reset('reset',_('Reset'),class_="btn")}
+        </div>
+   </div>
+</div>
+${h.end_form()}
+
+<script type="text/javascript">
+    function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
+        url = "${h.url('edit_user_group_perms', id=c.user_group.users_group_id)}";
+        var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
+        if (confirm(revoke_msg)){
+            ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
+        }
+    };
+
+    YUE.onDOMReady(function () {
+        if (!YUD.hasClass('perm_new_member_name', 'error')) {
+            YUD.setStyle('add_perm_input', 'display', 'none');
+        }
+        YAHOO.util.Event.addListener('add_perm', 'click', function () {
+            addPermAction(${_tmpl}, ${c.users_array|n}, ${c.user_groups_array|n});
+        });
+    });
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/user_groups/user_group_edit_settings.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,74 @@
+${h.form(url('users_group', id=c.user_group.users_group_id),method='put', id='edit_users_group')}
+    <div class="form">
+        <!-- fields -->
+            <div class="fields">
+                 <div class="field">
+                    <div class="label">
+                        <label for="users_group_name">${_('Group name')}:</label>
+                    </div>
+                    <div class="input">
+                        ${h.text('users_group_name',class_='large')}
+                    </div>
+                 </div>
+                 <div class="field">
+                    <div class="label label-textarea">
+                        <label for="user_group_description">${_('Description')}:</label>
+                    </div>
+                    <div class="textarea-small editor">
+                        ${h.textarea('user_group_description')}
+                        <span class="help-block">${_('Short, optional description for this user group.')}</span>
+                    </div>
+                 </div>
+                 <div class="field">
+                    <div class="label label-checkbox">
+                        <label for="users_group_active">${_('Active')}:</label>
+                    </div>
+                    <div class="checkboxes">
+                        ${h.checkbox('users_group_active',value=True)}
+                    </div>
+                 </div>
+                <div class="field">
+                    <div class="label">
+                        <label for="users_group_active">${_('Members')}:</label>
+                    </div>
+                    <div class="select">
+                        <table>
+                                <tr>
+                                    <td>
+                                        <div>
+                                            <div style="float:left">
+                                                <div class="text" style="padding: 0px 0px 6px;">${_('Chosen group members')}</div>
+                                                ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
+                                               <div id="remove_all_elements" style="cursor:pointer;text-align:center;margin: 4px; ">
+                                                   ${_('Remove all elements')}
+                                                   <i style="cursor:pointer; font-size: 16px;position: relative; bottom: -2px; padding: 2px" class="icon-chevron-right"></i>
+                                               </div>
+                                            </div>
+                                            <div style="float:left;width:20px;padding-top:50px">
+                                                <i style="cursor:pointer; padding: 4px; font-size: 16px" id="add_element" class="icon-chevron-left"></i>
+                                                <br />
+                                                <i style="cursor:pointer; padding: 4px; font-size: 16px" id="remove_element" class="icon-chevron-right"></i>
+                                            </div>
+                                            <div style="float:left">
+                                                 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
+                                                 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
+                                                 <div id="add_all_elements" style="cursor:pointer;text-align:center;margin: 4px;">
+                                                     <i style="cursor:pointer;font-size: 16px;position: relative; bottom: -2px; padding: 4px" class="icon-chevron-left"></i>${_('Add all elements')}
+                                                 </div>
+                                            </div>
+                                        </div>
+                                    </td>
+                                </tr>
+                        </table>
+                    </div>
+
+                </div>
+                <div class="buttons">
+                  ${h.submit('Save',_('Save'),class_="btn")}
+                </div>
+            </div>
+    </div>
+${h.end_form()}
+<script type="text/javascript">
+  MultiSelectWidget('users_group_members','available_members','edit_users_group');
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/user_groups/user_groups.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,61 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('User groups administration')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="breadcrumbs_links()">
+    <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
+    ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('admin')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+        <ul class="links">
+        %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
+          <li>
+            <a href="${h.url('new_users_group')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_(u'Add User Group')}</a>
+          </li>
+        %endif
+        </ul>
+    </div>
+    <!-- end box / title -->
+    <div class="table-grid table yui-skin-sam" id="datatable_list_wrap"></div>
+    <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
+</div>
+<script>
+  var data = ${c.data|n};
+  var fields = [
+    {key: "group_name"},
+    {key: "raw_name"},
+    {key: "desc"},
+    {key: "members"},
+    {key: "active"},
+    {key: "owner"},
+    {key: "action"},
+
+  ];
+  var column_defs = [
+    {key:"group_name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: nameSort }},
+    {key:"desc",label:"${_('Description')}",sortable:true},
+    {key:"members",label:"${_('Members')}",sortable:false},
+    {key:"active",label:"${_('Active')}",sortable:true,},
+    {key:"owner",label:"${_('Owner')}",sortable:true},
+    {key:"action",label:"${_('Action')}",sortable:false},
+  ];
+  var counter = YUD.get('user_group_count');
+  var sort_key = "group_name";
+  YUI_datatable(data, fields, column_defs, counter, sort_key, ${c.visual.admin_grid_items});
+</script>
+</%def>
--- a/rhodecode/templates/admin/users/user_add.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/users/user_add.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,14 +2,17 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Add user')} &middot; ${c.rhodecode_name}
+    ${_('Add user')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 <%def name="breadcrumbs_links()">
     ${h.link_to(_('Admin'),h.url('admin_home'))}
     &raquo;
     ${h.link_to(_('Users'),h.url('users'))}
     &raquo;
-    ${_('Add new user')}
+    ${_('Add User')}
 </%def>
 
 <%def name="page_nav()">
@@ -78,6 +81,8 @@
                 </div>
                 <div class="input">
                     ${h.text('email',class_='small')}
+                    ${h.hidden('extern_name', c.default_extern_type)}
+                    ${h.hidden('extern_type', c.default_extern_type)}
                 </div>
              </div>
 
@@ -91,10 +96,15 @@
              </div>
 
             <div class="buttons">
-              ${h.submit('save',_('Save'),class_="ui-btn large")}
+              ${h.submit('save',_('Save'),class_="btn")}
             </div>
         </div>
     </div>
     ${h.end_form()}
 </div>
 </%def>
+<script>
+    $(document).ready(function(){
+        $('#username').focus();
+    })
+</script>
--- a/rhodecode/templates/admin/users/user_edit.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/users/user_edit.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Edit user')} ${c.user.username} &middot; ${c.rhodecode_name}
+    ${_('%s user settings') % c.user.username}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -10,7 +13,7 @@
     &raquo;
     ${h.link_to(_('Users'),h.url('users'))}
     &raquo;
-    ${_('Edit %s') % c.user.username}
+    ${c.user.username}
 </%def>
 
 <%def name="page_nav()">
@@ -18,235 +21,36 @@
 </%def>
 
 <%def name="main()">
-<div class="box box-left">
-    <!-- box / title -->
+<div class="box">
     <div class="title">
         ${self.breadcrumbs()}
     </div>
-    <!-- end box / title -->
-    ${h.form(url('update_user', id=c.user.user_id),method='put')}
-    <div class="form">
-        <div class="field">
-           <div class="gravatar_box">
-               <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
-                <p>
-                %if c.use_gravatar:
-                <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
-                <br/>${_('Using')} ${c.user.email}
-                %else:
-                <br/>${c.user.email}
-                %endif
+
+    ##main
+    <div style="width: 150px; float:left">
+        <ul class="nav nav-pills nav-stacked">
+          <li>
+           <div class="gravatar_box" style="height: 26px">
+               <div class="gravatar" style="float: left">
+                   <img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/>
+               </div>
+               <div class="truncate" style="margin:10px 0px 10px 0px; color:#5f5f5f; float:left; width: 100px">
+                <strong>${c.user.username}</strong>
+               </div>
            </div>
-        </div>
-        <div class="field">
-            <div class="label">
-                <label>${_('API key')}:</label> ${c.user.api_key}
-            </div>
-        </div>
-        ##show current ip just if we show ourself
-        %if c.rhodecode_user.username == c.user.username:
-        <div class="field">
-            <div class="label">
-                <label>${_('Current IP')}:</label> ${c.perm_user.ip_addr or "?"}
-            </div>
-        </div>
-        %endif
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="username">${_('Username')}:</label>
-                </div>
-                <div class="input">
-                    %if c.ldap_dn:
-                        ${h.text('username',class_='medium disabled', readonly="readonly")}
-                    %else:
-                        ${h.text('username',class_='medium')}
-                    %endif:
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label">
-                    <label for="ldap_dn">${_('LDAP DN')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")}
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label">
-                    <label for="new_password">${_('New password')}:</label>
-                </div>
-                <div class="input">
-                    ${h.password('new_password',class_='medium',autocomplete="off")}
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label">
-                    <label for="password_confirmation">${_('New password confirmation')}:</label>
-                </div>
-                <div class="input">
-                    ${h.password('password_confirmation',class_="medium",autocomplete="off")}
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label">
-                    <label for="firstname">${_('First Name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('firstname',class_='medium')}
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label">
-                    <label for="lastname">${_('Last Name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('lastname',class_='medium')}
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label">
-                    <label for="email">${_('Email')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('email',class_='medium')}
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label for="active">${_('Active')}:</label>
-                </div>
-                <div class="checkboxes">
-                    ${h.checkbox('active',value=True)}
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label for="admin">${_('Admin')}:</label>
-                </div>
-                <div class="checkboxes">
-                    ${h.checkbox('admin',value=True)}
-                </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-</div>
-<div style="min-height:780px" class="box box-right">
-    <!-- box / title -->
-    <div class="title">
-        <h5>${_('Permissions')}</h5>
-    </div>
-    <%namespace name="dpb" file="/base/default_perms_box.html"/>
-    ${dpb.default_perms_box(url('user_perm', id=c.user.user_id))}
-
-    ## permissions overview
-    <%namespace name="p" file="/base/perms_summary.html"/>
-    ${p.perms_summary(c.perm_user.permissions, show_all=True)}
-
-</div>
-<div class="box box-left" style="clear:left">
-    <!-- box / title -->
-    <div class="title">
-        <h5>${_('Email addresses')}</h5>
+          </li>
+          <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', id=c.user.user_id)}">${_('Profile')}</a></li>
+          <li class="${'active' if c.active=='api_keys' else ''}"><a href="${h.url('edit_user_api_keys', id=c.user.user_id)}">${_('API keys')}</a></li>
+          <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', id=c.user.user_id)}">${_('Advanced')}</a></li>
+          <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_perms', id=c.user.user_id)}">${_('Default permissions')}</a></li>
+          <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', id=c.user.user_id)}">${_('Emails')}</a></li>
+          <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', id=c.user.user_id)}">${_('Ip whitelist')}</a></li>
+        </ul>
     </div>
 
-    <div class="emails_wrap">
-      <table class="noborder">
-      %for em in c.user_email_map:
-        <tr>
-            <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.user.email,16)}"/> </div></td>
-            <td><div class="email">${em.email}</div></td>
-            <td>
-              ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')}
-                  ${h.hidden('del_email',em.email_id)}
-                  ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
-                  class_="delete_icon action_button", onclick="return  confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
-              ${h.end_form()}
-            </td>
-        </tr>
-      %endfor
-      </table>
+    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
+        <%include file="/admin/users/user_edit_${c.active}.html"/>
     </div>
-
-    ${h.form(url('user_emails', id=c.user.user_id),method='put')}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="new_email">${_('New email address')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('new_email', class_='medium')}
-                </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Add'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
 </div>
-<div class="box box-left" style="clear:left">
-    <!-- box / title -->
-    <div class="title">
-        <h5>${_('Allowed IP addresses')}</h5>
-    </div>
 
-    <div class="ips_wrap">
-      <table class="noborder">
-      %if c.user_ip_map:
-        %for ip in c.user_ip_map:
-          <tr>
-              <td><div class="ip">${ip.ip_addr}</div></td>
-              <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
-              <td>
-                ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
-                    ${h.hidden('del_ip',ip.ip_id)}
-                    ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
-                    class_="delete_icon action_button", onclick="return  confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
-                ${h.end_form()}
-              </td>
-          </tr>
-        %endfor
-       %else:
-        <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
-       %endif
-      </table>
-    </div>
-
-    ${h.form(url('user_ips', id=c.user.user_id),method='put')}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="new_ip">${_('New ip address')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('new_ip', class_='medium')}
-                </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Add'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-</div>
 </%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/users/user_edit_advanced.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,25 @@
+<div style="font-size: 24px; color: #666666; padding: 0px 0px 10px 0px">${_('User: %s') % c.user.username}</div>
+
+<dl class="dl-horizontal">
+<%
+ elems = [
+    (_('Repositories'), len(c.user.repositories), ', '.join((x.repo_name for x in c.user.repositories))),
+    (_('Source of Record'), c.user.extern_type, ''),
+    (_('Created on'), h.fmt_date(c.user.created_on), ''),
+    (_('Last Login'), c.user.last_login or '-', ''),
+    (_('Member of User groups'), len(c.user.group_member), ', '.join((x.users_group.users_group_name for x in c.user.group_member)))
+ ]
+%>
+%for dt, dd, tt in elems:
+  <dt style="width:150px; text-align: left">${dt}:</dt>
+  <dd style="margin-left: 160px" title="${tt}">${dd}</dd>
+%endfor
+</dl>
+
+${h.form(h.url('delete_user', id=c.user.user_id),method='delete')}
+    <button class="btn btn-small btn-danger" type="submit"
+            onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');">
+        <i class="icon-remove-sign"></i>
+        ${_('Delete this user')}
+    </button>
+${h.end_form()}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/users/user_edit_api_keys.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,83 @@
+<div class="apikeys_wrap">
+  <table class="noborder">
+    <tr>
+        <td style="width: 450px"><div class="truncate autoexpand" style="width:120px;font-size:16px;font-family: monospace">${c.user.api_key}</div></td>
+        <td>
+            <span class="btn btn-mini btn-success disabled">${_('Built-in')}</span>
+        </td>
+        <td>${_('expires')}: ${_('never')}</td>
+        <td>
+            ${h.form(url('edit_user_api_keys', id=c.user.user_id),method='delete')}
+                ${h.hidden('del_api_key',c.user.api_key)}
+                ${h.hidden('del_api_key_builtin',1)}
+                <button class="btn btn-mini btn-danger" type="submit"
+                        onclick="return confirm('${_('Confirm to reset this api key: %s') % c.user.api_key}');">
+                    ${_('reset')}
+                </button>
+            ${h.end_form()}
+        </td>
+    </tr>
+    %if c.user_api_keys:
+        %for api_key in c.user_api_keys:
+          <tr class="${'expired' if api_key.expired else ''}">
+            <td style="width: 450px"><div class="truncate autoexpand" style="width:120px;font-size:16px;font-family: monospace">${api_key.api_key}</div></td>
+            <td>${api_key.description}</td>
+            <td style="min-width: 80px">
+                 %if api_key.expires == -1:
+                  ${_('expires')}: ${_('never')}
+                 %else:
+                    %if api_key.expired:
+                        ${_('expired')}: ${h.age(h.time_to_datetime(api_key.expires))}
+                    %else:
+                        ${_('expires')}: ${h.age(h.time_to_datetime(api_key.expires))}
+                    %endif
+                 %endif
+            </td>
+            <td>
+                ${h.form(url('edit_user_api_keys', id=c.user.user_id),method='delete')}
+                    ${h.hidden('del_api_key',api_key.api_key)}
+                    <button class="btn btn-mini btn-danger" type="submit"
+                            onclick="return confirm('${_('Confirm to remove this api key: %s') % api_key.api_key}');">
+                        <i class="icon-remove-sign"></i>
+                        ${_('remove')}
+                    </button>
+                ${h.end_form()}
+            </td>
+          </tr>
+        %endfor
+    %else:
+    <tr><td><div class="ip">${_('No additional api keys specified')}</div></td></tr>
+    %endif
+  </table>
+</div>
+
+<div>
+    ${h.form(url('edit_user_api_keys', id=c.user.user_id), method='put')}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+             <div class="field">
+                <div class="label">
+                    <label for="new_email">${_('New api key')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('description', class_='medium', placeholder=_('Description'))}
+                    ${h.select('lifetime', '', c.lifetime_options)}
+                </div>
+             </div>
+            <div class="buttons">
+              ${h.submit('save',_('Add'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
+            </div>
+        </div>
+    </div>
+    ${h.end_form()}
+</div>
+
+<script>
+    $(document).ready(function(){
+        $("#lifetime").select2({
+            'dropdownAutoWidth': true,
+        });
+    })
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/users/user_edit_emails.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,51 @@
+<div class="emails_wrap">
+  <table class="noborder">
+    <tr>
+    <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email,16)}"/> </div></td>
+    <td><div class="email">${c.user.email}</div></td>
+    <td>
+        <span class="btn btn-mini btn-success disabled">${_('Primary')}</span>
+    </td>
+    </tr>
+    %if c.user_email_map:
+        %for em in c.user_email_map:
+          <tr>
+            <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.email,16)}"/> </div></td>
+            <td><div class="email">${em.email}</div></td>
+            <td>
+                ${h.form(url('edit_user_emails', id=c.user.user_id),method='delete')}
+                    ${h.hidden('del_email_id',em.email_id)}
+                    <i class="icon-remove-sign" style="color:#FF4444"></i>
+                    ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
+                    class_="action_button", onclick="return  confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
+                ${h.end_form()}
+            </td>
+          </tr>
+        %endfor
+    %else:
+    <tr><td><div class="ip">${_('No additional emails specified')}</div></td></tr>
+    %endif
+  </table>
+</div>
+
+<div>
+    ${h.form(url('edit_user_emails', id=c.user.user_id),method='put')}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+             <div class="field">
+                <div class="label">
+                    <label for="new_email">${_('New email address')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('new_email', class_='medium')}
+                </div>
+             </div>
+            <div class="buttons">
+              ${h.submit('save',_('Add'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
+            </div>
+        </div>
+    </div>
+    ${h.end_form()}
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/users/user_edit_ips.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,55 @@
+<div class="ips_wrap">
+  <table class="noborder">
+    %if c.default_user_ip_map and c.inherit_default_ips:
+        %for ip in c.default_user_ip_map:
+          <tr>
+            <td><div class="ip">${ip.ip_addr}</div></td>
+            <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
+            <td>${h.literal(_('Inherited from %s') % h.link_to('*default*',h.url('admin_permissions_ips')))}</td>
+          </tr>
+        %endfor
+    %endif
+
+    %if c.user_ip_map:
+        %for ip in c.user_ip_map:
+          <tr>
+            <td><div class="ip">${ip.ip_addr}</div></td>
+            <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
+            <td>
+                ${h.form(url('edit_user_ips', id=c.user.user_id),method='delete')}
+                    ${h.hidden('del_ip_id',ip.ip_id)}
+                    <i class="icon-remove-sign" style="color:#FF4444"></i>
+                    ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
+                    class_="action_button", onclick="return  confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
+                ${h.end_form()}
+            </td>
+          </tr>
+        %endfor
+    %endif
+    %if not c.default_user_ip_map and not c.user_ip_map:
+        <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
+    %endif
+  </table>
+</div>
+
+<div>
+    ${h.form(url('edit_user_ips', id=c.user.user_id),method='put')}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+             <div class="field">
+                <div class="label">
+                    <label for="new_ip">${_('New ip address')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('new_ip', class_='medium')}
+                </div>
+             </div>
+            <div class="buttons">
+              ${h.submit('save',_('Add'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
+            </div>
+        </div>
+    </div>
+    ${h.end_form()}
+</div>
--- a/rhodecode/templates/admin/users/user_edit_my_account.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,232 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%def name="title()">
-    ${_('My account')} ${c.rhodecode_user.username} &middot; ${c.rhodecode_name}
-</%def>
-
-<%def name="breadcrumbs_links()">
-    ${_('My Account')}
-</%def>
-
-<%def name="page_nav()">
-    ${self.menu('admin')}
-</%def>
-
-<%def name="main()">
-
-<div class="box box-left">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    <!-- end box / title -->
-    ${c.form|n}
-</div>
-
-<div class="box box-right">
-    <!-- box / title -->
-    <div class="title">
-        <h5>
-        <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value="" style="display: none"/>
-        </h5>
-         <ul class="links" style="color:#DADADA">
-           <li>
-             <span><a id="show_perms" class="link-white current" href="#perms">${_('My permissions')}</a> </span>
-           </li>
-           <li>
-             <span><a id="show_my" class="link-white" href="#my">${_('My repos')}</a> </span>
-           </li>
-           <li>
-             <span><a id="show_pullrequests" class="link-white" href="#pullrequests">${_('My pull requests')}</a> </span>
-           </li>
-         </ul>
-    </div>
-    <!-- end box / title -->
-    ## permissions overview
-    <div id="perms_container">
-    <%namespace name="p" file="/base/perms_summary.html"/>
-    ${p.perms_summary(c.perm_user.permissions)}
-    </div>
-
-    <div id="my_container" style="display:none">
-        <div class="table yui-skin-sam" id="repos_list_wrap"></div>
-        <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
-    </div>
-    <div id="pullrequests_container" class="table" style="display:none">
-        ## loaded via AJAX
-        ${_('Loading...')}
-    </div>
-</div>
-
-<script type="text/javascript">
-pyroutes.register('admin_settings_my_pullrequests', "${url('admin_settings_my_pullrequests')}", []);
-
-var show_perms = function(e){
-    YUD.addClass('show_perms', 'current');
-    YUD.removeClass('show_my','current');
-    YUD.removeClass('show_pullrequests','current');
-
-    YUD.setStyle('my_container','display','none');
-    YUD.setStyle('pullrequests_container','display','none');
-    YUD.setStyle('perms_container','display','');
-    YUD.setStyle('q_filter','display','none');
-}
-YUE.on('show_perms','click',function(e){
-    show_perms();
-})
-
-var show_my = function(e){
-    YUD.addClass('show_my', 'current');
-    YUD.removeClass('show_perms','current');
-    YUD.removeClass('show_pullrequests','current');
-
-    YUD.setStyle('perms_container','display','none');
-    YUD.setStyle('pullrequests_container','display','none');
-    YUD.setStyle('my_container','display','');
-    YUD.setStyle('q_filter','display','');
-    if(!YUD.hasClass('show_my', 'loaded')){
-        table_renderer(${c.data |n});
-        YUD.addClass('show_my', 'loaded');
-    }
-}
-YUE.on('show_my','click',function(e){
-    show_my(e);
-})
-
-var show_pullrequests = function(e){
-    YUD.addClass('show_pullrequests', 'current');
-    YUD.removeClass('show_my','current');
-    YUD.removeClass('show_perms','current');
-
-    YUD.setStyle('my_container','display','none');
-    YUD.setStyle('perms_container','display','none');
-    YUD.setStyle('pullrequests_container','display','');
-    YUD.setStyle('q_filter','display','none');
-
-    var url = pyroutes.url('admin_settings_my_pullrequests');
-    if(YUD.get('show_closed') && YUD.get('show_closed').checked) {
-        var url = pyroutes.url('admin_settings_my_pullrequests', {'pr_show_closed': '1'});
-    }
-    ypjax(url, 'pullrequests_container', function(){
-        YUE.on('show_closed','change',function (e) {
-            show_pullrequests(e);
-        });
-    });
-}
-YUE.on('show_pullrequests','click',function(e){
-    show_pullrequests(e)
-})
-
-var tabs = {
-    'perms': show_perms,
-    'my': show_my,
-    'pullrequests': show_pullrequests
-}
-var url = location.href.split('#');
-if (url[1]) {
-    //We have a hash
-    var tabHash = url[1];
-    var func = tabs[tabHash]
-    if (func){
-        func();
-    }
-}
-
-function table_renderer(data){
-    var myDataSource = new YAHOO.util.DataSource(data);
-    myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
-
-    myDataSource.responseSchema = {
-        resultsList: "records",
-        fields: [
-            {key:"menu"},
-            {key:"raw_name"},
-            {key:"name"},
-            {key:"last_changeset"},
-            {key:"action"},
-            ]
-        };
-    myDataSource.doBeforeCallback = function(req,raw,res,cb) {
-        // This is the filter function
-        var data     = res.results || [],
-            filtered = [],
-            i,l;
-
-        if (req) {
-            req = req.toLowerCase();
-            for (i = 0; i<data.length; i++) {
-                var pos = data[i].raw_name.toLowerCase().indexOf(req)
-                if (pos != -1) {
-                    filtered.push(data[i]);
-                }
-            }
-            res.results = filtered;
-        }
-        return res;
-    }
-
-      // main table sorting
-      var myColumnDefs = [
-          {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
-          {key:"name",label:"${_('Name')}",sortable:true,
-              sortOptions: { sortFunction: nameSort }},
-          {key:"last_changeset",label:"${_('Tip')}",sortable:true,
-              sortOptions: { sortFunction: revisionSort }},
-          {key:"action",label:"${_('Action')}",sortable:false},
-      ];
-
-      var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
-        sortedBy:{key:"name",dir:"asc"},
-        paginator: YUI_paginator(50, ['user-paginator']),
-
-        MSG_SORTASC:"${_('Click to sort ascending')}",
-        MSG_SORTDESC:"${_('Click to sort descending')}",
-        MSG_EMPTY:"${_('No records found.')}",
-        MSG_ERROR:"${_('Data error.')}",
-        MSG_LOADING:"${_('Loading...')}",
-      }
-      );
-      myDataTable.subscribe('postRenderEvent',function(oArgs) {
-          tooltip_activate();
-          quick_repo_menu();
-      });
-
-      var filterTimeout = null;
-
-      updateFilter = function() {
-          // Reset timeout
-          filterTimeout = null;
-
-          // Reset sort
-          var state = myDataTable.getState();
-          state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
-
-          // Get filtered data
-          myDataSource.sendRequest(YUD.get('q_filter').value,{
-              success : myDataTable.onDataReturnInitializeTable,
-              failure : myDataTable.onDataReturnInitializeTable,
-              scope   : myDataTable,
-              argument: state
-          });
-
-      };
-      YUE.on('q_filter','click',function(){
-          if(!YUD.hasClass('q_filter', 'loaded')){
-              //TODO: load here full list later to do search within groups
-              YUD.addClass('q_filter', 'loaded');
-          }
-       });
-
-      YUE.on('q_filter','keyup',function (e) {
-          clearTimeout(filterTimeout);
-          filterTimeout = setTimeout(updateFilter,600);
-      });
-
-      if(YUD.get('q_filter').value) {
-        updateFilter();
-      }
-
-    }
-</script>
-</%def>
--- a/rhodecode/templates/admin/users/user_edit_my_account_form.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-<div>
-    ${h.form(url('admin_settings_my_account_update'),method='put')}
-        <div class="form">
-
-             <div class="field">
-                <div class="gravatar_box">
-                    <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
-                    <p>
-                    %if c.use_gravatar:
-                    <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
-                    <br/>${_('Using')} ${c.user.email}
-                    %else:
-                    <br/>${c.user.email}
-                    %endif
-                    </p>
-                </div>
-             </div>
-            <div class="field">
-                <div class="field">
-                    <div class="label">
-                        <label>${_('Current IP')}:</label> ${c.perm_user.ip_addr or "?"}
-                    </div>
-                </div>
-                <div class="label">
-                    <label>${_('API key - keep this in secret, change your password to generate new key')}</label>
-                    <div class="truncate autoexpand" style="width:120px;font-size:16px;font-family: monospace">${c.user.api_key}</div>
-                </div>
-            </div>
-            <div class="fields">
-                 <div class="field">
-                    <div class="label">
-                        <label for="username">${_('Username')}:</label>
-                    </div>
-                    <div class="input">
-                      %if c.ldap_dn:
-                          ${h.text('username',class_='medium disabled', readonly="readonly")}
-                      %else:
-                          ${h.text('username',class_='medium')}
-                      %endif:
-                    </div>
-                 </div>
-
-                 <div class="field">
-                    <div class="label">
-                        <label for="new_password">${_('New password')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.password('new_password',class_="medium",autocomplete="off")}
-                    </div>
-                 </div>
-
-                 <div class="field">
-                    <div class="label">
-                        <label for="password_confirmation">${_('New password confirmation')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.password('password_confirmation',class_="medium",autocomplete="off")}
-                    </div>
-                 </div>
-
-                 <div class="field">
-                    <div class="label">
-                        <label for="name">${_('First Name')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.text('firstname',class_="medium")}
-                    </div>
-                 </div>
-
-                 <div class="field">
-                    <div class="label">
-                        <label for="lastname">${_('Last Name')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.text('lastname',class_="medium")}
-                    </div>
-                 </div>
-
-                 <div class="field">
-                    <div class="label">
-                        <label for="email">${_('Email')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.text('email',class_="medium")}
-                    </div>
-                 </div>
-
-                <div class="buttons">
-                  ${h.submit('save',_('Save'),class_="ui-btn large")}
-                  ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-                </div>
-            </div>
-        </div>
-    ${h.end_form()}
-    </div>
--- a/rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-%if c.show_closed:
-  ${h.checkbox('show_closed',checked="checked", label=_('Show closed pull requests'))}
-%else:
-  ${h.checkbox('show_closed',label=_('Show closed pull requests'))}
-%endif
-<div class="pullrequests_section_head">${_('Opened by me')}</div>
-<ul>
-    %if c.my_pull_requests:
-      %for pull_request in c.my_pull_requests:
-      <li class="${'closed' if pull_request.is_closed() else ''}">
-        <div style="height: 12px">
-          <div style="float:left">
-            <img src="${h.url('/images/icons/flag_status_%s.png' % str(pull_request.last_review_status))}" />
-            <a href="${h.url('pullrequest_show',repo_name=pull_request.other_repo.repo_name,pull_request_id=pull_request.pull_request_id)}">
-              ${_('Pull request #%s opened on %s') % (pull_request.pull_request_id, h.fmt_date(pull_request.created_on))}
-              %if pull_request.is_closed():
-                (${_('Closed')})
-              %endif
-            </a>
-          </div>
-          <div style="float:left;margin-top: -5px">
-            ${h.form(url('pullrequest_delete', repo_name=pull_request.other_repo.repo_name, pull_request_id=pull_request.pull_request_id),method='delete')}
-              ${h.submit('remove_%s' % pull_request.pull_request_id,'',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
-            ${h.end_form()}
-          </div>
-        </div>
-      </li>
-      %endfor
-   %else:
-    <li><span class="empty_data">${_('Nothing here yet')}</span></li>
-   %endif
-</ul>
-
-<div class="pullrequests_section_head" style="clear:both">${_('I participate in')}</div>
-<ul>
-    %if c.participate_in_pull_requests:
-      %for pull_request in c.participate_in_pull_requests:
-      <li class="${'closed' if pull_request.is_closed() else ''}">
-        <div style="height: 12px">
-          <img src="${h.url('/images/icons/flag_status_%s.png' % str(pull_request.last_review_status))}" />
-          <a href="${h.url('pullrequest_show',repo_name=pull_request.other_repo.repo_name,pull_request_id=pull_request.pull_request_id)}">
-            ${_('Pull request #%s opened by %s on %s') % (pull_request.pull_request_id, pull_request.author.full_name, h.fmt_date(pull_request.created_on))}
-          </a>
-          %if pull_request.is_closed():
-            (${_('Closed')})
-          %endif
-        </div>
-      </li>
-      %endfor
-    %else:
-     <li><span class="empty_data">${_('Nothing here yet')}</span></li>
-    %endif
-</ul>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/users/user_edit_perms.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,6 @@
+<%namespace name="dpb" file="/base/default_perms_box.html"/>
+${dpb.default_perms_box(url('edit_user_perms', id=c.user.user_id))}
+
+## permissions overview
+<%namespace name="p" file="/base/perms_summary.html"/>
+${p.perms_summary(c.perm_user.permissions, show_all=True)}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/users/user_edit_profile.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,127 @@
+${h.form(url('update_user', id=c.user.user_id),method='put')}
+<div class="form">
+        <div class="field">
+           <div class="gravatar_box">
+               <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
+                <p>
+                %if c.visual.use_gravatar:
+                <strong>${_('Change avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
+                <br/>${_('Using')} ${c.user.email}
+                %else:
+                <strong>${_('Avatars are disabled')}</strong>
+                <br/>${c.user.email or _('Missing email, please update this user email address.')}
+                        ##show current ip just if we show ourself
+                        %if c.rhodecode_user.username == c.user.username:
+                            [${_('current IP')}: ${c.perm_user.ip_addr or "?"}]
+                        %endif
+                %endif
+           </div>
+        </div>
+        <% readonly = None %>
+        <% disabled = "" %>
+        <div class="fields">
+            %if c.extern_type != 'rhodecode':
+             <div class="field">
+               <% readonly = "readonly" %>
+               <% disabled = " disabled" %>
+               <strong>${_('This user is in an external Source of Record (%s); some details cannot be managed here.' % c.extern_type)}.</strong>
+             </div>
+            %endif
+
+             <div class="field">
+                <div class="label">
+                    <label for="username">${_('Username')}:</label>
+                </div>
+                <div class="input">
+                  ${h.text('username',class_='medium%s' % disabled, readonly=readonly)}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label">
+                    <label for="email">${_('Email')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('email',class_='medium')}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label">
+                    <label for="extern_type">${_('Source of Record')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('extern_type',class_='medium disabled',readonly="readonly")}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label">
+                    <label for="extern_name">${_('Name in Source of Record')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('extern_name',class_='medium disabled',readonly="readonly")}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label">
+                    <label for="new_password">${_('New password')}:</label>
+                </div>
+                <div class="input">
+                    ${h.password('new_password',class_='medium%s' % disabled,autocomplete="off",readonly=readonly)}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label">
+                    <label for="password_confirmation">${_('New password confirmation')}:</label>
+                </div>
+                <div class="input">
+                    ${h.password('password_confirmation',class_="medium%s" % disabled,autocomplete="off",readonly=readonly)}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label">
+                    <label for="firstname">${_('First Name')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('firstname',class_='medium')}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label">
+                    <label for="lastname">${_('Last Name')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('lastname',class_='medium')}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label label-checkbox">
+                    <label for="active">${_('Active')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('active',value=True)}
+                </div>
+             </div>
+
+             <div class="field">
+                <div class="label label-checkbox">
+                    <label for="admin">${_('Admin')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('admin',value=True)}
+                </div>
+             </div>
+
+            <div class="buttons">
+              ${h.submit('save',_('Save'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
+            </div>
+        </div>
+</div>
+${h.end_form()}
--- a/rhodecode/templates/admin/users/users.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/admin/users/users.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,11 +2,15 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('Users administration')} &middot; ${c.rhodecode_name}
+    ${_('Users administration')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
-    <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/> ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_count">0</span> ${_('users')}
+    <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
+    ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_count">0</span> ${_('users')}
 </%def>
 
 <%def name="page_nav()">
@@ -20,125 +24,44 @@
         ${self.breadcrumbs()}
         <ul class="links">
           <li>
-            <span>${h.link_to(_(u'Add new user'),h.url('new_user'))}</span>
+            <a href="${h.url('new_user')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_(u'Add User')}</a>
           </li>
         </ul>
     </div>
     <!-- end box / title -->
-    <div class="table yui-skin-sam" id="users_list_wrap"></div>
+    <div class="table-grid table yui-skin-sam" id="datatable_list_wrap"></div>
     <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
 </div>
 
 <script>
-  var url = "${h.url('formatted_users', format='json')}";
   var data = ${c.data|n};
-  var myDataSource = new YAHOO.util.DataSource(data);
-  myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
-
-  myDataSource.responseSchema = {
-      resultsList: "records",
-      fields: [
-          {key: "gravatar"},
-          {key: "raw_username"},
-          {key: "username"},
-          {key: "firstname"},
-          {key: "lastname"},
-          {key: "last_login"},
-          {key: "last_login_raw"},
-          {key: "active"},
-          {key: "admin"},
-          {key: "ldap"},
-          {key: "action"},
-      ]
-   };
-  myDataSource.doBeforeCallback = function(req,raw,res,cb) {
-      // This is the filter function
-      var data     = res.results || [],
-          filtered = [],
-          i,l;
-
-      if (req) {
-          req = req.toLowerCase();
-          for (i = 0; i<data.length; i++) {
-              var pos = data[i].raw_username.toLowerCase().indexOf(req)
-              if (pos != -1) {
-                  filtered.push(data[i]);
-              }
-          }
-          res.results = filtered;
-      }
-      YUD.get('user_count').innerHTML = res.results.length;
-      return res;
-  }
-
-  // main table sorting
-  var myColumnDefs = [
-      {key:"gravatar",label:"",sortable:false,},
-      {key:"username",label:"${_('Username')}",sortable:true,
-          sortOptions: { sortFunction: usernamelinkSort }
-      },
-      {key:"firstname",label:"${_('Firstname')}",sortable:true,},
-      {key:"lastname",label:"${_('Lastname')}",sortable:true,},
-      {key:"last_login",label:"${_('Last login')}",sortable:true,
-          sortOptions: { sortFunction: lastLoginSort }},
-      {key:"active",label:"${_('Active')}",sortable:true,},
-      {key:"admin",label:"${_('Admin')}",sortable:true,},
-      {key:"ldap",label:"${_('LDAP')}",sortable:true,},
-      {key:"action",label:"${_('Action')}",sortable:false},
+  var fields = [
+    {key: "gravatar"},
+    {key: "raw_name"},
+    {key: "username"},
+    {key: "firstname"},
+    {key: "lastname"},
+    {key: "last_login"},
+    {key: "last_login_raw"},
+    {key: "active"},
+    {key: "admin"},
+    {key: "extern_type"},
+    {key: "action"},
   ];
-
-  var myDataTable = new YAHOO.widget.DataTable("users_list_wrap", myColumnDefs, myDataSource,{
-    sortedBy:{key:"username",dir:"asc"},
-    paginator: new YAHOO.widget.Paginator({
-        rowsPerPage: 15,
-        alwaysVisible: false,
-        template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
-        pageLinks: 5,
-        containerClass: 'pagination-wh',
-        currentPageClass: 'pager_curpage',
-        pageLinkClass: 'pager_link',
-        nextPageLinkLabel: '&gt;',
-        previousPageLinkLabel: '&lt;',
-        firstPageLinkLabel: '&lt;&lt;',
-        lastPageLinkLabel: '&gt;&gt;',
-        containers:['user-paginator']
-    }),
-
-    MSG_SORTASC:"${_('Click to sort ascending')}",
-    MSG_SORTDESC:"${_('Click to sort descending')}",
-    MSG_EMPTY:"${_('No records found.')}",
-    MSG_ERROR:"${_('Data error.')}",
-    MSG_LOADING:"${_('Loading...')}",
-  }
-  );
-  myDataTable.subscribe('postRenderEvent',function(oArgs) {
-
-  });
-
-  var filterTimeout = null;
-
-  updateFilter  = function () {
-      // Reset timeout
-      filterTimeout = null;
-
-      // Reset sort
-      var state = myDataTable.getState();
-          state.sortedBy = {key:'username', dir:YAHOO.widget.DataTable.CLASS_ASC};
-
-      // Get filtered data
-      myDataSource.sendRequest(YUD.get('q_filter').value,{
-          success : myDataTable.onDataReturnInitializeTable,
-          failure : myDataTable.onDataReturnInitializeTable,
-          scope   : myDataTable,
-          argument: state
-      });
-
-  };
-
-  YUE.on('q_filter','keyup',function (e) {
-      clearTimeout(filterTimeout);
-      filterTimeout = setTimeout(updateFilter,600);
-  });
+  var column_defs = [
+    {key:"gravatar",label:"",sortable:false,},
+    {key:"username",label:"${_('Username')}",sortable:true},
+    {key:"firstname",label:"${_('Firstname')}",sortable:true,},
+    {key:"lastname",label:"${_('Lastname')}",sortable:true,},
+    {key:"last_login",label:"${_('Last login')}",sortable:true, sortOptions: { sortFunction: lastLoginSort }},
+    {key:"active",label:"${_('Active')}",sortable:true,},
+    {key:"admin",label:"${_('Admin')}",sortable:true,},
+    {key:"extern_type",label:"${_('Auth type')}",sortable:true,},
+    {key:"action",label:"${_('Action')}",sortable:false},
+  ];
+  var counter = YUD.get('user_count');
+  var sort_key = "username";
+  YUI_datatable(data, fields, column_defs, counter, sort_key, ${c.visual.admin_grid_items});
 </script>
 
 </%def>
--- a/rhodecode/templates/admin/users_groups/user_group_edit_perms.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-<table id="permissions_manage" class="noborder">
-    <tr>
-        <td>${_('none')}</td>
-        <td>${_('read')}</td>
-        <td>${_('write')}</td>
-        <td>${_('admin')}</td>
-        <td>${_('member')}</td>
-        <td></td>
-    </tr>
-    ## USERS
-    %for r2p in c.users_group.user_user_group_to_perm:
-        ##forbid revoking permission from yourself
-        <tr id="id${id(r2p.user.username)}">
-            %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin:
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none')}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read')}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write')}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin')}</td>
-            <td style="white-space: nowrap;">
-                <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
-            </td>
-            <td>
-              %if r2p.user.username !='default':
-                <span class="delete_icon action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
-                ${_('revoke')}
-                </span>
-              %endif
-            </td>
-            %else:
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none', disabled="disabled")}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read', disabled="disabled")}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write', disabled="disabled")}</td>
-            <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin', disabled="disabled")}</td>
-            <td style="white-space: nowrap;">
-                <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
-            </td>
-            <td>
-            </td>
-            %endif
-        </tr>
-    %endfor
-
-    ## USER GROUPS
-    %for g2p in c.users_group.user_group_user_group_to_perm:
-        <tr id="id${id(g2p.user_group.users_group_name)}">
-            <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.none')}</td>
-            <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.read')}</td>
-            <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.write')}</td>
-            <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.admin')}</td>
-            <td style="white-space: nowrap;">
-                <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.user_group.users_group_name}
-            </td>
-            <td>
-                <span class="delete_icon action_button" onclick="ajaxActionRevoke(${g2p.user_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.user_group.users_group_name)}', '${g2p.user_group.users_group_name}')">
-                ${_('revoke')}
-                </span>
-            </td>
-        </tr>
-    %endfor
-
-    <%
-    _tmpl = h.literal("""' \
-        <td><input type="radio" value="usergroup.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td><input type="radio" value="usergroup.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td><input type="radio" value="usergroup.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td><input type="radio" value="usergroup.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-        <td class="ac"> \
-            <div class="perm_ac" id="perm_ac_{0}"> \
-                <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
-                <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">  \
-                <div id="perm_container_{0}"></div> \
-            </div> \
-        </td> \
-        <td></td>'""")
-    %>
-    ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
-    <tr class="new_members last_new_member" id="add_perm_input"></tr>
-    <tr>
-        <td colspan="6">
-            <span id="add_perm" class="add_icon" style="cursor: pointer;">
-            ${_('Add another member')}
-            </span>
-        </td>
-    </tr>
-</table>
-<script type="text/javascript">
-function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
-    url = "${h.url('delete_user_group_perm_member', id=c.users_group.users_group_id)}";
-    var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
-    if (confirm(revoke_msg)){
-        ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
-    }
-};
-
-YUE.onDOMReady(function () {
-    if (!YUD.hasClass('perm_new_member_name', 'error')) {
-        YUD.setStyle('add_perm_input', 'display', 'none');
-    }
-    YAHOO.util.Event.addListener('add_perm', 'click', function () {
-        addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
-    });
-});
-
-</script>
--- a/rhodecode/templates/admin/users_groups/users_group_add.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%def name="title()">
-    ${_('Add user group')} &middot; ${c.rhodecode_name}
-</%def>
-<%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))}
-    &raquo;
-    ${h.link_to(_('User groups'),h.url('users_groups'))}
-    &raquo;
-    ${_('Add new user group')}
-</%def>
-
-<%def name="page_nav()">
-    ${self.menu('admin')}
-</%def>
-
-<%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    <!-- end box / title -->
-    ${h.form(url('users_groups'))}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="users_group_name">${_('Group name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('users_group_name',class_='small')}
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label for="users_group_active">${_('Active')}:</label>
-                </div>
-                <div class="checkboxes">
-                    ${h.checkbox('users_group_active',value=True, checked='checked')}
-                </div>
-             </div>
-
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="ui-btn large")}
-            </div>
-        </div>
-    </div>
-    ${h.end_form()}
-</div>
-</%def>
--- a/rhodecode/templates/admin/users_groups/users_group_edit.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,154 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%def name="title()">
-    ${_('Edit user group')} ${c.users_group.users_group_name} &middot; ${c.rhodecode_name}
-</%def>
-
-<%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))}
-    &raquo;
-    ${h.link_to(_('UserGroups'),h.url('users_groups'))}
-    &raquo;
-    ${_('Edit %s') % c.users_group.users_group_name}
-</%def>
-
-<%def name="page_nav()">
-    ${self.menu('admin')}
-</%def>
-
-<%def name="main()">
-<div class="box box-left" style="clear:left">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    <!-- end box / title -->
-    ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')}
-    <div class="form">
-        <!-- fields -->
-            <div class="fields">
-                 <div class="field">
-                    <div class="label">
-                        <label for="users_group_name">${_('Group name')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.text('users_group_name',class_='small')}
-                    </div>
-                 </div>
-
-                 <div class="field">
-                    <div class="label label-checkbox">
-                        <label for="users_group_active">${_('Active')}:</label>
-                    </div>
-                    <div class="checkboxes">
-                        ${h.checkbox('users_group_active',value=True)}
-                    </div>
-                 </div>
-                <div class="field">
-                    <div class="label">
-                        <label for="users_group_active">${_('Members')}:</label>
-                    </div>
-                    <div class="select">
-                        <table>
-                                <tr>
-                                    <td>
-                                        <div>
-                                            <div style="float:left">
-                                                <div class="text" style="padding: 0px 0px 6px;">${_('Chosen group members')}</div>
-                                                ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
-                                               <div id="remove_all_elements" style="cursor:pointer;text-align:center">
-                                                   ${_('Remove all elements')}
-                                                   <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
-                                               </div>
-                                            </div>
-                                            <div style="float:left;width:20px;padding-top:50px">
-                                                <img alt="add" id="add_element"
-                                                    style="padding:2px;cursor:pointer"
-                                                    src="${h.url('/images/icons/arrow_left.png')}"/>
-                                                <br />
-                                                <img alt="remove" id="remove_element"
-                                                    style="padding:2px;cursor:pointer"
-                                                    src="${h.url('/images/icons/arrow_right.png')}"/>
-                                            </div>
-                                            <div style="float:left">
-                                                 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
-                                                 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
-                                                 <div id="add_all_elements" style="cursor:pointer;text-align:center">
-                                                       <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
-                                                        ${_('Add all elements')}
-                                                 </div>
-                                            </div>
-                                        </div>
-                                    </td>
-                                </tr>
-                        </table>
-                    </div>
-
-                </div>
-                <div class="buttons">
-                  ${h.submit('Save',_('Save'),class_="ui-btn large")}
-                </div>
-            </div>
-    </div>
-${h.end_form()}
-    <div class="group_members_wrap">
-    % if c.group_members_obj:
-      <ul class="group_members">
-      %for user in c.group_members_obj:
-        <li>
-          <div class="group_member">
-            <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div>
-            <div>${h.link_to(user.username, h.url('edit_user',id=user.user_id))}</div>
-            <div>${user.full_name}</div>
-          </div>
-        </li>
-      %endfor
-      </ul>
-      %else:
-        <span class="empty_data">${_('No members yet')}</span>
-      %endif
-    </div>
-</div>
-
-<div class="box box-right">
-    <!-- box / title -->
-    <div class="title">
-        <h5>${_('Global Permissions')}</h5>
-    </div>
-    <%namespace name="dpb" file="/base/default_perms_box.html"/>
-    ${dpb.default_perms_box(url('users_group_perm', id=c.users_group.users_group_id))}
-
-    ## permissions overview
-    <%namespace name="p" file="/base/perms_summary.html"/>
-    ${p.perms_summary(c.permissions)}
-</div>
-
-<div class="box box-right" style="clear:right">
-    <div class="title">
-        <h5>${_('Permissions')}</h5>
-    </div>
-    ${h.form(url('set_user_group_perm_member', id=c.users_group.users_group_id),method='post')}
-    <div class="form">
-       <div class="fields">
-            <div class="field">
-                <div class="label">
-                    <label for="input">${_('Permissions')}:</label>
-                </div>
-                <div class="input">
-                    <%include file="user_group_edit_perms.html"/>
-                </div>
-            </div>
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
-            </div>
-       </div>
-    </div>
-    ${h.end_form()}
-</div>
-
-<script type="text/javascript">
-  MultiSelectWidget('users_group_members','available_members','edit_users_group');
-</script>
-</%def>
--- a/rhodecode/templates/admin/users_groups/users_groups.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%def name="title()">
-    ${_('User groups administration')} &middot; ${c.rhodecode_name}
-</%def>
-
-<%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))}
-    &raquo;
-    ${_('User groups')}
-</%def>
-
-<%def name="page_nav()">
-    ${self.menu('admin')}
-</%def>
-
-<%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
-        %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
-          <li>
-            <span>${h.link_to(_(u'Add new user group'),h.url('new_users_group'))}</span>
-          </li>
-        %endif
-        </ul>
-    </div>
-    <!-- end box / title -->
-
-    <div class="table">
-    %if c.users_groups_list:
-        <table class="table_disp">
-        <tr class="header">
-            <th class="left">${_('Group name')}</th>
-            <th class="left">${_('Members')}</th>
-            <th class="left">${_('Active')}</th>
-            <th class="left" colspan="2">${_('Action')}</th>
-        </tr>
-            %for cnt,u_group in enumerate(c.users_groups_list):
-                <tr class="parity${cnt%2}">
-                    <td>${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))}</td>
-                    <td><span class="tooltip" title="${h.tooltip(', '.join(map(h.safe_unicode,[x.user.username for x in u_group.members[:50]])))}">${len(u_group.members)}</span></td>
-                    <td>${h.boolicon(u_group.users_group_active)}</td>
-                    <td>
-                     <a href="${h.url('edit_users_group', id=u_group.users_group_id)}" title="${_('Edit')}">
-                       ${h.submit('edit_%s' % u_group.users_group_name,_('edit'),class_="edit_icon action_button")}
-                     </a>
-                    </td>
-                    <td>
-                        ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
-                            ${h.submit('remove_',_('delete'),id="remove_group_%s" % u_group.users_group_id,
-                            class_="delete_icon action_button",onclick="return  confirm('"+_('Confirm to delete this user group: %s') % u_group.users_group_name+"');")}
-                        ${h.end_form()}
-                    </td>
-                </tr>
-            %endfor
-        </table>
-    %else:
-        ${_('There are no user groups yet')}
-    %endif
-    </div>
-</div>
-</%def>
--- a/rhodecode/templates/base/base.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/base/base.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,11 +2,15 @@
 <%inherit file="root.html"/>
 
 <!-- HEADER -->
-<div id="header-dd"></div>
 <div id="header">
     <div id="header-inner" class="title">
         <div id="logo">
-            <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
+            <div class="header">
+                <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
+            </div>
+            %if c.rhodecode_name:
+             <div class="branding">- ${c.rhodecode_name}</div>
+            %endif
         </div>
         <!-- MENU -->
         ${self.page_nav()}
@@ -18,16 +22,7 @@
 
 <!-- CONTENT -->
 <div id="content">
-    <div class="flash_msg">
-        <% messages = h.flash.pop_messages() %>
-        % if messages:
-        <ul id="flash-messages">
-            % for message in messages:
-            <li class="${message.category}_msg">${message}</li>
-            % endfor
-        </ul>
-        % endif
-    </div>
+    ${self.flash_msg()}
     <div id="main">
         ${next.main()}
     </div>
@@ -42,15 +37,13 @@
                ${_('Server instance: %s') % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}
            </p>
            <p class="footer-link-right">
-               <a href="${h.url('rhodecode_official')}">
                RhodeCode
                %if c.visual.show_version:
                    ${c.rhodecode_version}
                %endif
-               </a>
-               &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski and others
+               &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
                %if c.rhodecode_bugtracker:
-                   &ndash; <a href="${c.rhodecode_bugtracker}">${_('Report a bug')}</a>
+                   &ndash; <a href="${c.rhodecode_bugtracker}" target="_blank">${_('Support')}</a>
                %endif
            </p>
        </div>
@@ -60,6 +53,11 @@
 <!-- END FOOTER -->
 
 ### MAKO DEFS ###
+
+<%def name="flash_msg()">
+    <%include file="/base/flash_msg.html"/>
+</%def>
+
 <%def name="breadcrumbs()">
     <div class="breadcrumbs">
     ${self.breadcrumbs_links()}
@@ -68,28 +66,31 @@
 
 <%def name="admin_menu()">
   <ul class="admin_menu">
-      <li>${h.link_to(_('Admin journal'),h.url('admin_home'),class_='journal ')}</li>
-      <li>${h.link_to(_('Repositories'),h.url('repos'),class_='repos')}</li>
-      <li>${h.link_to(_('Repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
-      <li>${h.link_to(_('Users'),h.url('users'),class_='users')}</li>
-      <li>${h.link_to(_('User groups'),h.url('users_groups'),class_='groups')}</li>
-      <li>${h.link_to(_('Permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
-      <li>${h.link_to(_('LDAP'),h.url('ldap_home'),class_='ldap')}</li>
-      <li>${h.link_to(_('Defaults'),h.url('defaults'),class_='defaults')}</li>
-      <li class="last">${h.link_to(_('Settings'),h.url('admin_settings'),class_='settings')}</li>
+      <li><a href="${h.url('admin_home')}"><i class="icon-book"></i> ${_('Admin journal')}</a></li>
+      <li><a href="${h.url('repos')}"><i class="icon-archive"></i> ${_('Repositories')}</a></li>
+      <li><a href="${h.url('repos_groups')}"><i class="icon-folder-close"></i> ${_('Repository groups')}</a></li>
+      <li><a href="${h.url('users')}"><i class="icon-user"></i> ${_('Users')}</a></li>
+      <li><a href="${h.url('users_groups')}"><i class="icon-group"></i> ${_('User groups')}</a></li>
+      <li><a href="${h.url('admin_permissions')}"><i class="icon-ban-circle"></i> ${_('Permissions')}</a></li>
+      <li><a href="${h.url('auth_home')}"><i class="icon-key"></i> ${_('Authentication')}</a></li>
+      <li><a href="${h.url('defaults')}"><i class="icon-wrench"></i> ${_('Defaults')}</a></li>
+      <li class="last"><a href="${h.url('admin_settings')}"><i class="icon-cog"></i> ${_('Settings')}</a></li>
   </ul>
+
 </%def>
 
+
+## admin menu used for people that have some admin resources
 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
   <ul>
    %if repositories:
-      <li>${h.link_to(_('Repositories'),h.url('repos'),class_='repos')}</li>
+      <li><a href="${h.url('repos')}"><i class="icon-archive"></i> ${_('Repositories')}</a></li>
    %endif
    %if repository_groups:
-      <li>${h.link_to(_('Repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
+      <li><a href="${h.url('repos_groups')}"><i class="icon-folder-close"></i> ${_('Repository groups')}</a></li>
    %endif
    %if user_groups:
-      <li>${h.link_to(_('User groups'),h.url('users_groups'),class_='groups')}</li>
+      <li><a href="${h.url('users_groups')}"><i class="icon-group"></i> ${_('User groups')}</a></li>
    %endif
   </ul>
 </%def>
@@ -110,31 +111,60 @@
 
   <!--- CONTEXT BAR -->
   <div id="context-bar" class="box">
+      <h2>
+        %if h.is_hg(c.rhodecode_db_repo):
+          <i class="icon-hg" style="color: #316293; font-size: 24px"></i>
+        %endif
+        %if h.is_git(c.rhodecode_db_repo):
+          <i class="icon-git" style="color: #e85634; font-size: 24px"></i>
+        %endif
+
+        ## public/private
+        %if c.rhodecode_db_repo.private:
+          <i class="icon-lock"></i>
+        %else:
+          <i class="icon-unlock-alt"></i>
+        %endif
+        ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
+
+        %if current == 'createfork':
+         - ${_('Create fork')}
+        %endif
+      </h2>
+      <!--
       <div id="breadcrumbs">
         ${h.link_to(_(u'Repositories'),h.url('home'))}
         &raquo;
         ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
       </div>
+      -->
       <ul id="context-pages" class="horizontal-list">
-        <li ${is_current('summary')}><a href="${h.url('summary_home', repo_name=c.repo_name)}" class="summary">${_('Summary')}</a></li>
-        <li ${is_current('changelog')}><a href="${h.url('changelog_home', repo_name=c.repo_name)}" class="changelogs">${_('Changelog')}</a></li>
-        <li ${is_current('files')}><a href="${h.url('files_home', repo_name=c.repo_name)}" class="files"></span>${_('Files')}</a></li>
+        <li ${is_current('summary')}><a href="${h.url('summary_home', repo_name=c.repo_name)}"><i class="icon-file-text"></i> ${_('Summary')}</a></li>
+        <li ${is_current('changelog')}><a href="${h.url('changelog_home', repo_name=c.repo_name)}"><i class="icon-time"></i> ${_('Changelog')}</a></li>
+        <li ${is_current('files')}><a href="${h.url('files_home', repo_name=c.repo_name)}"><i class="icon-file"></i> ${_('Files')}</a></li>
         <li ${is_current('switch-to')}>
-          <a href="#" id="branch_tag_switcher_2" class="dropdown switch-to"></span>${_('Switch To')}</a>
+          <a href="#" id="branch_tag_switcher_2" class="dropdown"><i class="icon-random"></i> ${_('Switch To')}</a>
           <ul id="switch_to_list_2" class="switch_to submenu">
             <li><a href="#">${_('Loading...')}</a></li>
           </ul>
         </li>
         <li ${is_current('options')}>
-          <a href="#" class="dropdown options"></span>${_('Options')}</a>
+             %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
+               <a href="${h.url('edit_repo',repo_name=c.repo_name)}" class="dropdown"><i class="icon-cogs"></i> ${_('Options')}</a>
+             %else:
+               <a href="#" class="dropdown"><i class="icon-cogs"></i> ${_('Options')}</a>
+             %endif
           <ul>
              %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
-                   <li>${h.link_to(_('Settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
+                   <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}"><i class="icon-cog"></i> ${_('Settings')}</a></li>
              %endif
               %if c.rhodecode_db_repo.fork:
-               <li>${h.link_to(_('Compare fork'),h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default', merge=1),class_='compare_request')}</li>
+               <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type=c.rhodecode_db_repo.landing_rev[0],org_ref=c.rhodecode_db_repo.landing_rev[1], other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],other_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
+                   <i class="icon-loop"></i> ${_('Compare fork')}</a></li>
               %endif
-              <li>${h.link_to(_('Search'),h.url('search_repo',repo_name=c.repo_name),class_='search')}</li>
+              <li><a href="${h.url('compare_home',repo_name=c.repo_name)}"><i class="icon-loop"></i> ${_('Compare')}</a></li>
+
+              <li><a href="${h.url('search_repo',repo_name=c.repo_name)}"><i class="icon-search"></i> ${_('Search')}</a></li>
 
               %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
                 %if c.rhodecode_db_repo.locked[0]:
@@ -148,19 +178,19 @@
               %if c.rhodecode_user.username != 'default':
                   <li>
                    <a class="${follow_class()}" onclick="javascript:toggleFollowingRepo(this,${c.rhodecode_db_repo.repo_id},'${str(h.get_token())}');">
-                    <span class="show-follow">${_('Follow')}</span>
-                    <span class="show-following">${_('Unfollow')}</span>
+                    <span class="show-follow"><i class="icon-heart-empty"></i> ${_('Follow')}</span>
+                    <span class="show-following"><i class="icon-heart"></i> ${_('Unfollow')}</span>
                   </a>
                   </li>
-                  <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}" class="fork">${_('Fork')}</a></li>
+                  <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}"><i class="icon-code-fork"></i> ${_('Fork')}</a></li>
                   %if h.is_hg(c.rhodecode_repo):
-                  <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="pull-request">${_('Create Pull Request')}</a></li>
+                  <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}"><i class="icon-code-fork"></i> ${_('Create Pull Request')}</a></li>
                   %endif
               %endif
              </ul>
         </li>
         <li ${is_current('showpullrequest')}>
-          <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}" class="pull-request">${_('Pull Requests')}
+          <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}"> <i class="icon-code-fork"></i> ${_('Pull Requests')}
             %if c.repository_pull_requests:
               <span>${c.repository_pull_requests}</span>
             %endif
@@ -234,7 +264,7 @@
                         %endif
                         </div>
                         <div class="submit">
-                            ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
+                            ${h.submit('sign_in',_('Log In'),class_="btn btn-mini")}
                         </div>
                     </div>
                 </div>
@@ -249,7 +279,7 @@
             <div class="links_right">
             <ol class="links">
               <li><a href="${h.url('notifications')}">${_('Notifications')}: ${c.unread_notifications}</a></li>
-              <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
+              <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
               <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
             </ol>
             </div>
@@ -269,58 +299,52 @@
         <ul id="quick" class="horizontal-list">
           <!-- repo switcher -->
           <li ${is_current('repositories')}>
-              <a class="menu_link repo_switcher childs" id="repo_switcher" title="${_('Switch repository')}" href="${h.url('home')}">
-              ${_('Repositories')}
-              </a>
-              <ul id="repo_switcher_list" class="repo_switcher">
-                  <li>
-                      <a href="#">${_('Loading...')}</a>
-                  </li>
-              </ul>
+            <input id="repo_switcher" name="repo_switcher" type="hidden">
           </li>
+
           ##ROOT MENU
           %if c.rhodecode_user.username != 'default':
             <li ${is_current('journal')}>
-              <a class="menu_link journal" title="${_('Show recent activity')}"  href="${h.url('journal')}">
-              ${_('Journal')}
+              <a class="menu_link" title="${_('Show recent activity')}"  href="${h.url('journal')}">
+                <i class="icon-book"></i> ${_('Journal')}
               </a>
             </li>
           %else:
             <li ${is_current('journal')}>
-              <a class="menu_link journal" title="${_('Public journal')}"  href="${h.url('public_journal')}">
-              ${_('Public journal')}
+              <a class="menu_link" title="${_('Public journal')}"  href="${h.url('public_journal')}">
+                <i class="icon-book"></i> ${_('Public journal')}
               </a>
             </li>
           %endif
             <li ${is_current('gists')}>
-              <a class="menu_link gists childs" title="${_('Show public gists')}"  href="${h.url('gists')}">
-              ${_('Gists')}
+              <a class="menu_link childs" title="${_('Show public gists')}"  href="${h.url('gists')}">
+                <i class="icon-file-2"></i> ${_('Gists')}
               </a>
                 <ul class="admin_menu">
-                  <li>${h.link_to(_('Create new gist'),h.url('new_gist'),class_='gists-new ')}</li>
-                  <li>${h.link_to(_('All public gists'),h.url('gists'),class_='gists ')}</li>
+                  <li><a href="${h.url('new_gist', public=1)}"><i class="icon-file-alt"></i> ${_('Create new gist')}</a></li>
+                  <li><a href="${h.url('gists')}"><i class="icon-copy"></i> ${_('All public gists')}</a></li>
                   %if c.rhodecode_user.username != 'default':
-                    <li>${h.link_to(_('My public gists'),h.url('gists', public=1),class_='gists')}</li>
-                    <li>${h.link_to(_('My private gists'),h.url('gists', private=1),class_='gists-private ')}</li>
+                    <li><a href="${h.url('gists', public=1)}"><i class="icon-copy"></i> ${_('My public gists')}</a></li>
+                    <li><a href="${h.url('gists', private=1)}"><i class="icon-file-text"></i> ${_('My private gists')}</a></li>
                   %endif
                 </ul>
             </li>
           <li ${is_current('search')}>
-              <a class="menu_link search" title="${_('Search in repositories')}"  href="${h.url('search')}">
-              ${_('Search')}
+              <a class="menu_link" title="${_('Search in repositories')}"  href="${h.url('search')}">
+                <i class="icon-search"></i> ${_('Search')}
               </a>
           </li>
           % if h.HasPermissionAll('hg.admin')('access admin main page'):
             <li ${is_current('admin')}>
-              <a class="menu_link admin childs" title="${_('Admin')}" href="${h.url('admin_home')}">
-                ${_('Admin')}
+              <a class="menu_link childs" title="${_('Admin')}" href="${h.url('admin_home')}">
+                <i class="icon-cog"></i> ${_('Admin')}
               </a>
               ${admin_menu()}
             </li>
           % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
           <li ${is_current('admin')}>
-              <a class="menu_link admin childs" title="${_('Admin')}">
-                ${_('Admin')}
+              <a class="menu_link childs" title="${_('Admin')}">
+                <i class="icon-cog"></i> ${_('Admin')}
               </a>
               ${admin_menu_simple(c.rhodecode_user.repositories_admin,
                                   c.rhodecode_user.repository_groups_admin,
@@ -328,34 +352,238 @@
           </li>
           % endif
           ${usermenu()}
-<script type="text/javascript">
-    YUE.on('repo_switcher','mouseover',function(){
-      var target = 'q_filter_rs';
-      var qfilter_activate = function(){
-          var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
-          var func = function(node){
-              return node.parentNode;
-          }
-          q_filter(target,nodes,func);
-      }
+
+    <script type="text/javascript">
+        var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
+        var cache = {}
+        /*format the look of items in the list*/
+        var format = function(state){
+            if (!state.id){
+              return state.text; // optgroup
+            }
+            var obj_dict = state.obj;
+            var tmpl = '';
+
+            if(obj_dict && state.type == 'repo'){
+                if(obj_dict['repo_type'] === 'hg'){
+                    tmpl += '<i class="icon-hg"></i> ';
+                }
+                else if(obj_dict['repo_type'] === 'git'){
+                    tmpl += '<i class="icon-git"></i> ';
+                }
+                if(obj_dict['private']){
+                    tmpl += '<i class="icon-lock" style="color: #e85634;"></i> ';
+                }
+                else if(visual_show_public_icon){
+                    tmpl += '<i class="icon-unlock-alt"></i> ';
+                }
+            }
+            if(obj_dict && state.type == 'group'){
+                    tmpl += '<i class="icon-folder-close"></i> ';
+            }
+            tmpl += state.text;
+            return tmpl;
+        }
+
+        $("#repo_switcher").select2({
+            placeholder: '<i class="icon-archive"></i> ${_('Repositories')} <i class="icon-caret-down"></i>',
+            dropdownAutoWidth: true,
+            formatResult: format,
+            formatSelection: format,
+            formatNoMatches: function(term){
+                return "${_('No matches found')}";
+            },
+            containerCssClass: "repo-switcher",
+            dropdownCssClass: "repo-switcher-dropdown",
+            escapeMarkup: function(m){
+                // don't escape our custom placeholder
+                if(m.substr(0,28) == '<i class="icon-archive"></i>'){
+                    return m;
+                }
+
+                return Select2.util.escapeMarkup(m);
+            },
+            query: function(query){
+              var key = 'cache';
+              var cached = cache[key] ;
+              if(cached) {
+                var data = {results: []};
+                //filter results
+                $.each(cached.results, function(){
+                    var section = this.text;
+                    var children = [];
+                    $.each(this.children, function(){
+                        if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
+                            children.push({'id': this.id, 'text': this.text, 'type': this.type, 'obj': this.obj})
+                        }
+                    })
+                    if(children.length !== 0){
+                        data.results.push({'text': section, 'children': children})
+                    }
+
+                });
+                query.callback(data);
+              }else{
+                  $.ajax({
+                    url: "${h.url('repo_switcher_data')}",
+                    data: {},
+                    dataType: 'json',
+                    type: 'GET',
+                    success: function(data) {
+                      cache[key] = data;
+                      query.callback({results: data.results});
+                    }
+                  })
+              }
+            },
+        });
+
+        $("#repo_switcher").on('select2-selecting', function(e){
+            e.preventDefault();
+            window.location = pyroutes.url('summary_home', {'repo_name': e.val})
+        })
+
+        ## Global mouse bindings ##
+
+        // general help "?"
+        Mousetrap.bind(['?'], function(e) {
+            $('#help_kb').modal({})
+        });
+
+        // / open the quick filter
+        Mousetrap.bind(['/'], function(e) {
+            $("#repo_switcher").select2("open");
+
+            // return false to prevent default browser behavior
+            // and stop event from bubbling
+            return false;
+        });
 
-      var loaded = YUD.hasClass('repo_switcher','loaded');
-      if(!loaded){
-         YUD.addClass('repo_switcher','loaded');
-         ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
-             function(o){qfilter_activate();YUD.get(target).focus()},
-             function(o){YUD.removeClass('repo_switcher','loaded');}
-             ,null);
-      }else{
-         YUD.get(target).focus();
-      }
-      return false;
-     });
+        // ctrl/command+b, show the the main bar
+        Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
+            if($('#header-inner').hasClass('hover') && $('#content').hasClass('hover')){
+                $('#header-inner').removeClass('hover');
+                $('#content').removeClass('hover');
+            }
+            else{
+                $('#header-inner').addClass('hover');
+                $('#content').addClass('hover');
+            }
+            return false;
+        });
+
+        // general nav g + action
+        Mousetrap.bind(['g h'], function(e) {
+            window.location = pyroutes.url('home');
+        });
+        Mousetrap.bind(['g g'], function(e) {
+            window.location = pyroutes.url('gists', {'private':1});
+        });
+        Mousetrap.bind(['g G'], function(e) {
+            window.location = pyroutes.url('gists', {'public':1});
+        });
+        Mousetrap.bind(['n g'], function(e) {
+            window.location = pyroutes.url('new_gist');
+        });
+        Mousetrap.bind(['n r'], function(e) {
+            window.location = pyroutes.url('new_repo');
+        });
+
+        % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
+            // nav in repo context
+            Mousetrap.bind(['g s'], function(e) {
+                window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
+            });
+            Mousetrap.bind(['g c'], function(e) {
+                window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
+            });
+            Mousetrap.bind(['g F'], function(e) {
+                window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
+            });
+            Mousetrap.bind(['g f'], function(e) {
+                window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
+            });
+            Mousetrap.bind(['g o'], function(e) {
+                window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
+            });
+            Mousetrap.bind(['g O'], function(e) {
+                window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
+            });
+        % endif
+
+    </script>
+</%def>
 
-     YUE.on('header-dd', 'click',function(e){
-         YUD.addClass('header-inner', 'hover');
-         YUD.addClass('content', 'hover');
-     });
-
-</script>
-</%def>
+<div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+          <h4 class="modal-title">${_('Keyboard shortcuts')}</h4>
+        </div>
+        <div class="modal-body">
+           <div class="row">
+              <div class="col-md-5">
+                <table class="keyboard-mappings">
+                    <tbody>
+                  <tr>
+                    <th></th>
+                    <th>${_('Site-wide shortcuts')}</th>
+                  </tr>
+                  <%
+                     elems = [
+                         ('/', 'Open quick search box'),
+                         ('ctrl/cmd+b', 'Show main settings bar'),
+                         ('g h', 'Goto home page'),
+                         ('g g', 'Goto my private gists page'),
+                         ('g G', 'Goto my public gists page'),
+                         ('n r', 'New repository page'),
+                         ('n g', 'New gist page'),
+                     ]
+                  %>
+                  %for key, desc in elems:
+                  <tr>
+                    <td class="keys">
+                      <span class="key">${key}</span>
+                    </td>
+                    <td>${desc}</td>
+                  </tr>
+                %endfor
+                </tbody>
+                  </table>
+              </div>
+              <div class="col-md-offset-5">
+                <table class="keyboard-mappings">
+                <tbody>
+                  <tr>
+                    <th></th>
+                    <th>${_('Repositories')}</th>
+                  </tr>
+                  <%
+                     elems = [
+                         ('g s', 'Goto summary page'),
+                         ('g c', 'Goto changelog page'),
+                         ('g f', 'Goto files page'),
+                         ('g F', 'Goto files page with file search activated'),
+                         ('g o', 'Goto repository settings'),
+                         ('g O', 'Goto repository permissions settings'),
+                     ]
+                  %>
+                  %for key, desc in elems:
+                  <tr>
+                    <td class="keys">
+                      <span class="key">${key}</span>
+                    </td>
+                    <td>${desc}</td>
+                  </tr>
+                %endfor
+                </tbody>
+            </table>
+              </div>
+            </div>
+        </div>
+        <div class="modal-footer">
+        </div>
+      </div><!-- /.modal-content -->
+    </div><!-- /.modal-dialog -->
+</div><!-- /.modal -->
--- a/rhodecode/templates/base/default_perms_box.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/base/default_perms_box.html	Wed Jul 02 19:03:13 2014 -0400
@@ -10,48 +10,59 @@
         <!-- fields -->
         <div class="fields">
              <div class="field">
-                <div class="checkboxes">
-                    <label for="inherit_default_permissions">${_('Inherit default permissions')}:</label>
-                    ${h.checkbox('inherit_default_permissions',value=True)}
+                <div class="label label-checkbox">
+                    <label for="inherit_default_permissions">${_('Inherit from defaults')}:</label>
                 </div>
-                <span class="help-block">
-                ${h.literal(_('Select to inherit permissions from %s settings. '
-                              'With this selected below options does not apply.')
-                              % h.link_to('default', url('edit_permission', id='default')))}
-                </span>
+                <div class="checkboxes">
+                    ${h.checkbox('inherit_default_permissions',value=True)}
+                    <span class="help-block">
+                    ${h.literal(_('Select to inherit permissions from %s permissions settings, and default IP address whitelist.')
+                                % h.link_to('default global', url('admin_permissions')))}
+                    </span>
+                </div>
              </div>
+
              <div id="inherit_overlay">
              <div class="field">
-                <div class="checkboxes">
+                <div class="label label-checkbox">
                     <label for="create_repo_perm">${_('Create repositories')}:</label>
-                    ${h.checkbox('create_repo_perm',value=True)}
                 </div>
-                <span class="help-block">
-                ${h.literal(_('Select this option to allow repository creation for this user'))}
-                </span>
+                <div class="checkboxes">
+                    ${h.checkbox('create_repo_perm',value=True)}
+                    <span class="help-block">
+                    ${h.literal(_('Select this option to allow repository creation for this user'))}
+                    </span>
+                </div>
              </div>
+
              <div class="field">
-                <div class="checkboxes">
+                <div class="label label-checkbox">
                     <label for="create_user_group_perm">${_('Create user groups')}:</label>
-                    ${h.checkbox('create_user_group_perm',value=True)}
                 </div>
-                <span class="help-block">
-                ${h.literal(_('Select this option to allow user group creation for this user'))}
-                </span>
+                <div class="checkboxes">
+                    ${h.checkbox('create_user_group_perm',value=True)}
+                    <span class="help-block">
+                        ${h.literal(_('Select this option to allow user group creation for this user'))}
+                    </span>
+                </div>
              </div>
+
              <div class="field">
-                <div class="checkboxes">
+                <div class="label label-checkbox">
                     <label for="fork_repo_perm">${_('Fork repositories')}:</label>
-                    ${h.checkbox('fork_repo_perm',value=True)}
                 </div>
-                <span class="help-block">
-                ${h.literal(_('Select this option to allow repository forking for this user'))}
-                </span>
+                <div class="checkboxes">
+                    ${h.checkbox('fork_repo_perm',value=True)}
+                    <span class="help-block">
+                        ${h.literal(_('Select this option to allow repository forking for this user'))}
+                    </span>
+                </div>
              </div>
-             </div>
+
+            </div>
             <div class="buttons">
-              ${h.submit('save',_('Save'),class_="ui-btn large")}
-              ${h.reset('reset',_('Reset'),class_="ui-btn large")}
+              ${h.submit('save',_('Save'),class_="btn")}
+              ${h.reset('reset',_('Reset'),class_="btn")}
             </div>
         </div>
     </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/base/flash_msg.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,16 @@
+<div class="flash_msg">
+    <% messages = h.flash.pop_messages() %>
+    % if messages:
+        % for message in messages:
+            <div class="alert alert-dismissable alert-${message.category}">
+              <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
+              ${message}
+            </div>
+        % endfor
+    % endif
+    <script>
+    if (typeof jQuery != 'undefined') {
+        $(".alert").alert();
+    }
+    </script>
+</div>
--- a/rhodecode/templates/base/perms_summary.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/base/perms_summary.html	Wed Jul 02 19:03:13 2014 -0400
@@ -3,10 +3,21 @@
 ##    <%namespace name="p" file="/base/perms_summary.html"/>
 ##    ${p.perms_summary(c.perm_user.permissions)}
 
-<%def name="perms_summary(permissions, show_all=False)">
+<%def name="perms_summary(permissions, show_all=False, actions=True)">
 <div id="perms" class="table">
      %for section in sorted(permissions.keys()):
-        <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
+        <div class="perms_section_head">
+            ${section.replace("_"," ").capitalize()}
+            %if section != 'global':
+                <div style="float: right">
+                ${_('show')}:
+                ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')}   <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
+                ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')}   <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
+                ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
+                ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
+                </div>
+            %endif
+        </div>
         %if not permissions[section]:
             <span class="empty_data">${_('No permissions defined yet')}</span>
         %else:
@@ -17,7 +28,9 @@
               <thead>
                   <tr>
                   <th colspan="2" class="left">${_('Permission')}</th>
+                  %if actions:
                   <th class="left">${_('Edit Permission')}</th>
+                  %endif
               </thead>
               <tbody>
               %for k in permissions[section]:
@@ -25,23 +38,28 @@
                       <td colspan="2">
                           ${h.get_permission_name(k)}
                       </td>
+                      %if actions:
                       <td>
-                           <a href="${h.url('edit_permission', id='default')}">${_('edit')}</a>
+                           <a href="${h.url('admin_permissions')}">${_('edit')}</a>
                       </td>
+                      %endif
                   </tr>
               %endfor
               </tbody>
           %else:
+             ## none/read/write/admin permissions on groups/repos etc
               <thead>
                   <tr>
                   <th class="left">${_('Name')}</th>
                   <th class="left">${_('Permission')}</th>
+                  %if actions:
                   <th class="left">${_('Edit Permission')}</th>
+                  %endif
               </thead>
-              <tbody>
-              %for k, section_perm in sorted(permissions[section].items(), key=lambda s: s[1]+s[0].lower()):
+              <tbody class="section_${section}">
+              %for k, section_perm in sorted(permissions[section].items(), key=lambda s: {'none':0, 'read':1,'write':2,'admin':3}.get(s[1].split('.')[-1])):
                   %if section_perm.split('.')[-1] != 'none' or show_all:
-                  <tr>
+                  <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
                       <td>
                           %if section == 'repositories':
                               <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
@@ -55,18 +73,21 @@
                       <td>
                            <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
                       </td>
+                      %if actions:
                       <td>
                           %if section == 'repositories':
-                              <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
+                              <a href="${h.url('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
                           %elif section == 'repositories_groups':
-                              <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
+                              <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
                           %elif section == 'user_groups':
                               ##<a href="${h.url('edit_users_group',id=k)}">${_('edit')}</a>
                           %endif
                       </td>
+                      %endif
                   </tr>
                   %endif
               %endfor
+              <tr id="empty_${section}" style="display: none"><td colspan="6">${_('No permission defined')}</td></tr>
               </tbody>
           %endif
          </table>
@@ -74,4 +95,38 @@
         %endif
      %endfor
 </div>
+<script>
+    $(document).ready(function(){
+        var show_empty = function(section){
+            var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
+            console.log(visible)
+            console.log($('.section_{0} tr.perm_row:visible'.format(section)))
+            if(visible == 0){
+                $('#empty_{0}'.format(section)).show();
+            }
+            else{
+                $('#empty_{0}'.format(section)).hide();
+            }
+        }
+        $('.perm_filter').on('change', function(e){
+            var self = this;
+            var section = $(this).attr('section');
+
+            var opts = {}
+            var elems = $('.filter_' + section).each(function(el){
+                var perm_type = $(this).attr('perm_type');
+                var checked = this.checked;
+                opts[perm_type] = checked;
+                if(checked){
+                    $('.'+section+'_'+perm_type).show();
+                }
+                else{
+                    $('.'+section+'_'+perm_type).hide();
+                }
+            });
+            show_empty(section);
+        })
+
+    })
+</script>
 </%def>
--- a/rhodecode/templates/base/root.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/base/root.html	Wed Jul 02 19:03:13 2014 -0400
@@ -10,9 +10,12 @@
 
         ## CSS ###
         <%def name="css()">
-            <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css', ver=c.rhodecode_version)}" media="screen"/>
+            <link rel="stylesheet" type="text/css" href="${h.url('/css/fontawesome_extension.css')}" media="screen"/>
+            <link rel="stylesheet" type="text/css" href="${h.url('/css/fontawesome.css')}" media="screen"/>
+            <link rel="stylesheet" type="text/css" href="${h.url('/js/select2/select2.css', ver=c.rhodecode_version)}"/>
             <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css', ver=c.rhodecode_version)}"/>
-            <link rel="stylesheet" type="text/css" href="${h.url('/css/contextbar.css', ver=c.rhodecode_version)}"/>
+            <link rel="stylesheet" type="text/css" href="${h.url('/css/newstyle.css', ver=c.rhodecode_version)}" media="screen"/>
+            <link rel="stylesheet" type="text/css" href="${h.url('/css/bootstrap.css', ver=c.rhodecode_version)}" media="screen"/>
             ## EXTRA FOR CSS
             ${self.css_extra()}
         </%def>
@@ -59,6 +62,17 @@
                 'Expand diff': "${_('Expand diff')}",
                 'Failed to revoke permission': "${_('Failed to revoke permission')}",
                 'Confirm to revoke permission for {0}: {1} ?': "${_('confirm to revoke permission for {0}: {1} ?')}",
+                'enabled': "${_('enabled')}",
+                'disabled': "${_('disabled')}",
+                'Select changeset': "${_('Select changeset')}",
+                'specify changeset': "${_('specify changeset')}",
+                'MSG_SORTASC': "${_('Click to sort ascending')}",
+                'MSG_SORTDESC': "${_('Click to sort descending')}",
+                'MSG_EMPTY': "${_('No records found.')}",
+                'MSG_ERROR': "${_('Data error.')}",
+                'MSG_LOADING': "${_('Loading...')}",
+
+
             };
             var _TM = TRANSLATION_MAP;
 
@@ -70,6 +84,10 @@
             %endif
             </script>
             <script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.rhodecode_version)}"></script>
+            <script type="text/javascript" src="${h.url('/js/jquery-1.10.2.min.js', ver=c.rhodecode_version)}"></script>
+            <script type="text/javascript" src="${h.url('/js/bootstrap.js', ver=c.rhodecode_version)}"></script>
+            <script type="text/javascript" src="${h.url('/js/select2/select2.js', ver=c.rhodecode_version)}"></script>
+            <script type="text/javascript" src="${h.url('/js/mousetrap.js', ver=c.rhodecode_version)}"></script>
             <!--[if lt IE 9]>
                <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
             <![endif]-->
@@ -95,10 +113,23 @@
               show_more_event();
               show_changeset_tooltip();
               // routes registration
+              pyroutes.register('home', "${h.url('home')}", []);
+              pyroutes.register('new_gist', "${h.url('new_gist')}", []);
+              pyroutes.register('gists', "${h.url('gists')}", []);
+              pyroutes.register('new_repo', "${h.url('new_repo')}", []);
+
+              pyroutes.register('summary_home', "${h.url('summary_home', repo_name='%(repo_name)s')}", ['repo_name']);
+              pyroutes.register('changelog_home', "${h.url('changelog_home', repo_name='%(repo_name)s')}", ['repo_name']);
+              pyroutes.register('files_home', "${h.url('files_home', repo_name='%(repo_name)s',revision='%(revision)s',f_path='%(f_path)s')}", ['repo_name', 'revision', 'f_path']);
+              pyroutes.register('edit_repo', "${h.url('edit_repo', repo_name='%(repo_name)s')}", ['repo_name']);
+              pyroutes.register('edit_repo_perms', "${h.url('edit_repo_perms', repo_name='%(repo_name)s')}", ['repo_name']);
+              pyroutes.register('pullrequest_home', "${h.url('pullrequest_home', repo_name='%(repo_name)s')}", ['repo_name']);
+
               pyroutes.register('toggle_following', "${h.url('toggle_following')}");
               pyroutes.register('changeset_info', "${h.url('changeset_info', repo_name='%(repo_name)s', revision='%(revision)s')}", ['repo_name', 'revision']);
               pyroutes.register('repo_size', "${h.url('repo_size', repo_name='%(repo_name)s')}", ['repo_name']);
               pyroutes.register('changeset_comment_preview', "${h.url('changeset_comment_preview', repo_name='%(repo_name)s')}", ['repo_name']);
+              pyroutes.register('repo_refs_data', "${h.url('repo_refs_data', repo_name='%(repo_name)s')}", ['repo_name']);
            })
             </script>
         </%def>
--- a/rhodecode/templates/bookmarks/bookmarks.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/bookmarks/bookmarks.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Bookmarks') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s Bookmarks') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -23,7 +26,7 @@
     </div>
     <!-- end box / title -->
     %if c.repo_bookmarks:
-    <div class="info_box" id="compare_bookmarks" style="clear: both;padding: 10px 19px;text-align: right;"><a href="#" class="ui-btn small">${_('Compare bookmarks')}</a></div>
+    <div class="info_box" id="compare_bookmarks" style="clear: both;padding: 10px 19px;text-align: right;"><a href="#" class="btn btn-mini">${_('Compare Bookmarks')}</a></div>
     %endif
     <div class="table">
         <%include file='bookmarks_data.html'/>
@@ -45,7 +48,8 @@
 });
 // main table sorting
 var myColumnDefs = [
-    {key:"name",label:"${_('Name')}",sortable:true},
+    {key:"name",label:"${_('Name')}",sortable:true,
+        sortOptions: { sortFunction: nameSort }},
     {key:"date",label:"${_('Date')}",sortable:true,
         sortOptions: { sortFunction: dateSort }},
     {key:"author",label:"${_('Author')}",sortable:true},
@@ -60,9 +64,12 @@
 
 myDataSource.responseSchema = {
     fields: [
+        {key:"raw_name"},
         {key:"name"},
+        {key:"raw_date"},
         {key:"date"},
         {key:"author"},
+        {key:"last_rev_raw"},
         {key:"revision"},
         {key:"compare"},
     ]
--- a/rhodecode/templates/bookmarks/bookmarks_data.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/bookmarks/bookmarks_data.html	Wed Jul 02 19:03:13 2014 -0400
@@ -3,23 +3,29 @@
     <table id="bookmarks_data">
     <thead>
         <tr>
+            <th class="left">Raw name</th> ##notranslation
             <th class="left">${_('Name')}</th>
+            <th class="left">Raw date</th> ##notranslation
             <th class="left">${_('Date')}</th>
             <th class="left">${_('Author')}</th>
+            <th class="left">Raw rev</th> ##notranslation
             <th class="left">${_('Revision')}</th>
             <th class="left">${_('Compare')}</th>
         </tr>
     </thead>
     %for cnt,book in enumerate(c.repo_bookmarks.items()):
         <tr class="parity${cnt%2}">
+            <td>${book[0]}</td>
             <td>
                 <span class="logbooks">
                     <span class="booktag">${h.link_to(book[0],
                     h.url('files_home',repo_name=c.repo_name,revision=book[1].raw_id))}</span>
                 </span>
             </td>
+            <td>${book[1]._timestamp}</td>
             <td><span class="tooltip" title="${h.tooltip(h.age(book[1].date))}">${h.fmt_date(book[1].date)}</span></td>
             <td title="${book[1].author}">${h.person(book[1].author)}</td>
+            <td>${book[1].revision}</td>
             <td>
               <div>
                   <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=book[1].raw_id)}">r${book[1].revision}:${h.short_id(book[1].raw_id)}</a></pre>
--- a/rhodecode/templates/branches/branches.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/branches/branches.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Branches') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s Branches') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -23,7 +26,7 @@
     </div>
     <!-- end box / title -->
     %if c.repo_branches:
-    <div class="info_box" id="compare_branches" style="clear: both;padding: 10px 19px;text-align: right;"><a href="#" class="ui-btn small">${_('Compare branches')}</a></div>
+    <div class="info_box" id="compare_branches" style="clear: both;padding: 10px 19px;text-align: right;"><a href="#" class="btn btn-mini">${_('Compare Branches')}</a></div>
     %endif
     <div class="table">
         <%include file='branches_data.html'/>
@@ -44,7 +47,8 @@
 });
 // main table sorting
 var myColumnDefs = [
-    {key:"name",label:"${_('Name')}",sortable:true},
+    {key:"name",label:"${_('Name')}",sortable:true,
+        sortOptions: { sortFunction: nameSort }},
     {key:"date",label:"${_('Date')}",sortable:true,
         sortOptions: { sortFunction: dateSort }},
     {key:"author",label:"${_('Author')}",sortable:true},
@@ -59,9 +63,12 @@
 
 myDataSource.responseSchema = {
     fields: [
+        {key:"raw_name"},
         {key:"name"},
+        {key:"raw_date"},
         {key:"date"},
         {key:"author"},
+        {key:"last_rev_raw"},
         {key:"revision"},
         {key:"compare"},
     ]
--- a/rhodecode/templates/branches/branches_data.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/branches/branches_data.html	Wed Jul 02 19:03:13 2014 -0400
@@ -3,23 +3,29 @@
     <table id="branches_data">
       <thead>
         <tr>
+            <th class="left">Raw name</th> ##notranslation
             <th class="left">${_('Name')}</th>
+            <th class="left">Raw date</th> ##notranslation
             <th class="left">${_('Date')}</th>
             <th class="left">${_('Author')}</th>
+            <th class="left">Raw rev</th> ##notranslation
             <th class="left">${_('Revision')}</th>
             <th class="left">${_('Compare')}</th>
         </tr>
       </thead>
         %for cnt,branch in enumerate(c.repo_branches.items()):
         <tr class="parity${cnt%2}">
+            <td>${branch[0]}</td>
             <td>
                 <span class="logtags">
                     <span class="branchtag">${h.link_to(branch[0],
                     h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
                 </span>
             </td>
+            <td>${branch[1]._timestamp}</td>
             <td><span class="tooltip" title="${h.tooltip(h.age(branch[1].date))}">${h.fmt_date(branch[1].date)}</span></td>
             <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
+            <td>${branch[1].revision}</td>
             <td>
                 <div>
                     <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre>
@@ -31,17 +37,21 @@
             </td>
         </tr>
         %endfor
+        ## closed branches if any
         % if hasattr(c,'repo_closed_branches') and c.repo_closed_branches:
           %for cnt,branch in enumerate(c.repo_closed_branches.items()):
           <tr class="parity${cnt%2}">
+              <td>${branch[0]}</td>
               <td>
                   <span class="logtags">
                       <span class="branchtag">${h.link_to(branch[0]+' [closed]',
                       h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
                   </span>
               </td>
+              <td>${branch[1]._timestamp}</td>
               <td><span class="tooltip" title="${h.tooltip(h.age(branch[1].date))}">${h.fmt_date(branch[1].date)}</span></td>
               <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
+              <td>${branch[1].revision}</td>
               <td>
                 <div>
                     <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre>
--- a/rhodecode/templates/changelog/changelog.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/changelog/changelog.html	Wed Jul 02 19:03:13 2014 -0400
@@ -3,11 +3,13 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-${_('%s Changelog') % c.repo_name} &middot;
-%if c.changelog_for_path:
-  /${c.changelog_for_path} &middot;
-%endif
-${c.rhodecode_name}
+    ${_('%s Changelog') % c.repo_name}
+    %if c.changelog_for_path:
+      /${c.changelog_for_path}
+    %endif
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -34,32 +36,34 @@
         % if c.pagination:
             <div id="graph">
                 <div style="display:${'none' if c.changelog_for_path else ''}">
-                    <div class="info_box" style="clear: both;padding: 10px 6px;min-height: 12px;text-align: right;">
-                        <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
-                        <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
+                    <div class="container_header">
+                       <div style="float:right; margin: 0px 0px 0px 4px">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
+                       <div class="info_box" style="text-align: right; float: right">
+                            <a href="#" class="btn btn-mini" id="rev_range_container" style="display:none"></a>
+                            <a href="#" class="btn btn-mini" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
 
-                        %if c.rhodecode_db_repo.fork:
-                            <a id="compare_fork" title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default',merge=1)}" class="ui-btn small">${_('Compare fork with parent')}</a>
-                        %endif
-                        %if h.is_hg(c.rhodecode_repo):
-                        <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
-                        %endif
-                    </div>
-                    <div class="container_header">
-                        ${h.form(h.url.current(),method='get')}
-                        <div style="float:left">
-                            ${h.submit('set',_('Show'),class_="ui-btn")}
-                            ${h.text('size',size=1,value=c.size)}
-                            ${_('revisions')}
-                        </div>
-                        ${h.end_form()}
-                        <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
+                            %if c.rhodecode_db_repo.fork:
+                                <a id="compare_fork"
+                                   title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}"
+                                   href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type=c.rhodecode_db_repo.landing_rev[0],org_ref=c.rhodecode_db_repo.landing_rev[1],other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],other_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}"
+                                   class="btn btn-mini"><i class="icon-loop"></i> ${_('Compare fork with Parent(%s)' % c.rhodecode_db_repo.fork.repo_name)}</a>
+                            %endif
+                            <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="btn btn-mini">${_('Open new pull request')}</a>
+                       </div>
+
+                       ${h.form(h.url.current(),method='get')}
+                       <div style="float:left">
+                           ${h.submit('set',_('Show'),class_="btn btn-mini")}
+                           ${h.text('size',size=1,value=c.size)}
+                           ${_('revisions')}
+                       </div>
+                       ${h.end_form()}
                     </div>
                 </div>
                 <div id="graph_nodes">
                     <canvas id="graph_canvas"></canvas>
                 </div>
-                <div id="graph_content">
+                <div id="graph_content" style="${'margin: 0px' if c.changelog_for_path else ''}">
 
                 <table id="changesets">
                 <tbody>
@@ -98,16 +102,19 @@
                         <td class="date">
                             <div class="date">${h.age(cs.date,True)}</div>
                         </td>
+                        <td class="expand_commit" commit_id="${cs.raw_id}" title="${_('Expand commit message')}">
+                            <i class="icon-resize-vertical" style="color:#DDD"></i>
+                        </td>
                         <td class="mid">
                             <div class="log-container">
-                                <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
-                                <div class="expand"><span class="expandtext">&darr; ${_('Show more')} &darr;</span></div>
+                                <div class="message" id="C-${cs.raw_id}">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
                                 <div class="extra-container">
                                     %if c.comments.get(cs.raw_id):
                                         <div class="comments-container">
                                             <div class="comments-cnt" title="${_('Changeset has comments')}">
                                                 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
                                                     ${len(c.comments[cs.raw_id])}
+                                                    <i class="icon-comment-alt icon-comment-colored"></i>
                                                 </a>
                                             </div>
                                         </div>
@@ -152,8 +159,6 @@
                 var checkboxes = YUD.getElementsByClassName('changeset_range');
                 // register our routes needed for this view
                 pyroutes.register('changeset_home', "${h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s')}", ['repo_name', 'revision']);
-                pyroutes.register('changelog_home', "${h.url('changelog_home', repo_name='%(repo_name)s')}", ['repo_name']);
-                pyroutes.register('pullrequest_home', "${h.url('pullrequest_home', repo_name='%(repo_name)s')}", ['repo_name']);
 
                 var checkbox_checker = function(e){
                     var checked_checkboxes = [];
@@ -239,17 +244,16 @@
                         YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
                     };
                 }
-                YUE.on(YUQ('.expand'),'click',function(e){
-                    var elem = e.currentTarget.parentNode.parentNode;
-                    YUD.setStyle(e.currentTarget,'display','none');
-                    YUD.setStyle(elem,'height','auto');
 
+                $('.expand_commit').on('click',function(e){
+                    $(this).children('i').hide();
+                    var cid = $(this).attr('commit_id');
+                    $('#C-'+cid).css({'height': 'auto', 'margin': '4px 0px 4px 0px'})
                     //redraw the graph, line_count and jsdata are global vars
                     set_canvas(100);
 
                     var r = new BranchRenderer();
                     r.render(jsdata,100,line_count);
-
                 });
 
                 // change branch filter
@@ -289,6 +293,7 @@
                 r.render(jsdata,100,line_count);
 
             });
+
         </script>
         %else:
             ${_('There are no changes yet')}
--- a/rhodecode/templates/changelog/changelog_summary_data.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/changelog/changelog_summary_data.html	Wed Jul 02 19:03:13 2014 -0400
@@ -13,17 +13,8 @@
         <td>
           <div>
             <div class="changeset-status-container">
-              %if c.comments.get(cs.raw_id,[]):
-               <div class="comments-container">
-                   <div class="comments-cnt" title="${('comments')}">
-                       <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
-                           ${len(c.comments[cs.raw_id])}
-                       </a>
-                   </div>
-               </div>
-              %endif
               %if c.statuses.get(cs.raw_id):
-                <div class="changeset-status-ico">
+                <div class="changeset-status-ico shortlog">
                 %if c.statuses.get(cs.raw_id)[2]:
                   <a class="tooltip" title="${_('Changeset status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
                     <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
@@ -33,6 +24,15 @@
                 %endif
                 </div>
               %endif
+              %if c.comments.get(cs.raw_id,[]):
+               <div class="comments-container">
+                   <div title="${('comments')}">
+                       <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
+                          <i class="icon-comment-alt icon-comment-colored"></i> ${len(c.comments[cs.raw_id])}
+                       </a>
+                   </div>
+               </div>
+              %endif
             </div>
             <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}">${h.show_id(cs)}</a></pre>
          </div>
@@ -86,7 +86,7 @@
 <h4>${_('Add or upload files directly via RhodeCode')}</h4>
 <div style="margin: 20px 30px;">
   <div id="add_node_id" class="add_node">
-      <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='')}">${_('Add new file')}</a>
+      <a class="btn btn-mini" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='', anchor='edit')}">${_('Add New File')}</a>
   </div>
 </div>
 %endif
--- a/rhodecode/templates/changeset/changeset.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/changeset/changeset.html	Wed Jul 02 19:03:13 2014 -0400
@@ -3,7 +3,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Changeset') % c.repo_name} - ${h.show_id(c.changeset)} &middot; ${c.rhodecode_name}
+    ${_('%s Changeset') % c.repo_name} - ${h.show_id(c.changeset)}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -23,50 +26,41 @@
     </div>
     <script>
     var _USERS_AC_DATA = ${c.users_array|n};
-    var _GROUPS_AC_DATA = ${c.users_groups_array|n};
+    var _GROUPS_AC_DATA = ${c.user_groups_array|n};
     AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
     AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
     </script>
     <div class="table">
         <div class="diffblock">
             <div class="parents">
-                %if c.changeset.parents:
-                 %for n, p_cs in enumerate(reversed(c.changeset.parents)):
-                    <span class="changeset_hash">&laquo; ${h.link_to('%s:%s' % (p_cs.revision,p_cs.raw_id[:6]),h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span>
-                    <br>
-                 %endfor
-                 %else:
-                    <span>${_('No parents')}</span>
-                 %endif
+                <div id="parent_link" class="changeset_hash">
+                    <i style="color:#036185" class="icon-chevron-left"></i> <a href="#">${_('parent rev.')}</a>
+                </div>
             </div>
+
             <div class="children">
-                %if c.changeset.children:
-                 %for n, p_cs in enumerate(reversed(c.changeset.children)):
-                    <span class="changeset_hash">${h.link_to('%s:%s' % (p_cs.revision,p_cs.raw_id[:6]),h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)} &raquo;</span>
-                    <br>
-                 %endfor
-                 %else:
-                    <span>${_('No children')}</span>
-                 %endif
+                <div id="child_link" class="changeset_hash">
+                    <a href="#">${_('child rev.')}</a> <i style="color:#036185" class="icon-chevron-right"></i>
+                </div>
             </div>
+
             <div class="code-header banner">
-
-                <div class="hash">
-                 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
-                </div>
-                <div class="date">
-                  ${h.fmt_date(c.changeset.date)}
-                </div>
                 <div class="changeset-status-container">
                     %if c.statuses:
-                      <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
-                      <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div>
+                        <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div>
+                        <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
                     %endif
                 </div>
                 <div class="diff-actions">
-                  <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}"  class="tooltip" title="${h.tooltip(_('Raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
-                  <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}"  class="tooltip" title="${h.tooltip(_('Patch diff'))}"><img class="icon" src="${h.url('/images/icons/page_add.png')}"/></a>
-                  <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}"  class="tooltip" title="${h.tooltip(_('Download diff'))}"><img class="icon" src="${h.url('/images/icons/page_save.png')}"/></a>
+                  <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}"  class="tooltip" title="${h.tooltip(_('Raw diff'))}">
+                      <img class="icon" src="${h.url('/images/icons/page_white.png')}"/>
+                  </a>
+                  <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}"  class="tooltip" title="${h.tooltip(_('Patch diff'))}">
+                      <img class="icon" src="${h.url('/images/icons/page_add.png')}"/>
+                  </a>
+                  <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
+                      <img class="icon" src="${h.url('/images/icons/page_save.png')}"/>
+                  </a>
                   ${c.ignorews_url(request.GET)}
                   ${c.context_url(request.GET)}
                 </div>
@@ -75,18 +69,9 @@
         </div>
         <div id="changeset_content">
             <div class="container">
-                 <div class="left">
-                     <div class="author">
-                         <div class="gravatar">
-                             <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.changeset.author),20)}"/>
-                         </div>
-                         <span>${h.person(c.changeset.author)}</span><br/>
-                         <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
-                     </div>
-                     <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
-                 </div>
-                 <div class="right">
-                     <div class="changes">
+
+                <div class="right">
+                    <div class="changes">
                         % if (len(c.changeset.affected_files) <= c.affected_files_cut_off) or c.fulldiff:
                          <span class="removed" title="${_('Removed')}">${len(c.changeset.removed)}</span>
                          <span class="changed" title="${_('Changed')}">${len(c.changeset.changed)}</span>
@@ -96,38 +81,51 @@
                          <span class="changed" title="${_('Affected %s files') % len(c.changeset.affected_files)}">!</span>
                          <span class="added"   title="${_('Affected %s files') % len(c.changeset.affected_files)}">!</span>
                         % endif
-                     </div>
+                    </div>
+
+                    <span class="logtags">
+                        %if len(c.changeset.parents)>1:
+                        <span class="merge">${_('merge')}</span>
+                        %endif
 
-                 <span class="logtags">
-                 %if len(c.changeset.parents)>1:
-                 <span class="merge">${_('merge')}</span>
-                 %endif
-                    %if h.is_hg(c.rhodecode_repo):
-                      %for book in c.changeset.bookmarks:
-                      <span class="booktag" title="${_('Bookmark %s') % book}">
-                         ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
-                      </span>
-                      %endfor
-                    %endif
-                     %for tag in c.changeset.tags:
+                        %if h.is_hg(c.rhodecode_repo):
+                          %for book in c.changeset.bookmarks:
+                          <span class="booktag" title="${_('Bookmark %s') % book}">
+                             ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
+                          </span>
+                          %endfor
+                        %endif
+
+                        %for tag in c.changeset.tags:
                          <span class="tagtag"  title="${_('Tag %s') % tag}">
                          ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
-                     %endfor
-                     %if c.changeset.branch:
+                        %endfor
+
+                        %if c.changeset.branch:
                          <span class="branchtag" title="${_('Branch %s') % c.changeset.branch}">
                          ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
                          </span>
-                     %endif
-                 </span>
-                    </div>
+                        %endif
+                    </span>
+                </div>
+                <div class="left">
+                     <div class="author">
+                         <div class="gravatar">
+                             <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.changeset.author),20)}"/>
+                         </div>
+                         <span><b>${h.person(c.changeset.author)}</b> - ${h.age(c.changeset.date,True)} ${h.fmt_date(c.changeset.date)}</span><br/>
+                         <span>${h.email_or_none(c.changeset.author)}</span><br/>
+                     </div>
+                     <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
+                </div>
             </div>
-            <span>
+            <div class="changes_txt">
             % if c.limited_diff:
             ${ungettext('%s file changed','%s files changed',len(c.changeset.affected_files)) % (len(c.changeset.affected_files))}:
             % else:
             ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.changeset.affected_files)) % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}:
             %endif
-            </span>
+            </div>
             <div class="cs_files">
               %for FID, (cs1, cs2, change, path, diff, stats) in c.changes[c.changeset.raw_id].iteritems():
                   <div class="cs_${change}">
@@ -198,6 +196,85 @@
           // inject comments into they proper positions
           var file_comments = YUQ('.inline-comment-placeholder');
           renderInlineComments(file_comments);
+
+          pyroutes.register('changeset_home',
+                            "${h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s')}",
+                            ['repo_name', 'revision']);
+
+          //next links
+          $('#child_link').on('click', function(e){
+              //fetch via ajax what is going to be the next link, if we have
+              //>1 links show them to user to choose
+              if(!$('#child_link').hasClass('disabled')){
+                  $.ajax({
+                    url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.changeset.raw_id)}',
+                    success: function(data) {
+                      if(data.results.length === 0){
+                          $('#child_link').addClass('disabled');
+                          $('#child_link').html('${_('no revisions')}');
+                      }
+                      if(data.results.length === 1){
+                          var commit = data.results[0];
+                          window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
+                      }
+                      else if(data.results.length === 2){
+                          $('#child_link').addClass('disabled');
+                          $('#child_link').addClass('double');
+                          var _html = '';
+                          _html +='<a title="__title__" href="__url__">__rev__</a> <i style="color:#036185" class="icon-chevron-right"></i>'
+                                  .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
+                                  .replace('__title__', data.results[0].message)
+                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
+                          _html +='<br/>'
+                          _html +='<a title="__title__" href="__url__">__rev__</a> <i style="color:#036185" class="icon-chevron-right"></i>'
+                                  .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
+                                  .replace('__title__', data.results[1].message)
+                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
+                          $('#child_link').html(_html);
+                      }
+                    },
+                  });
+              e.preventDefault();
+              }
+          })
+
+          //prev links
+          $('#parent_link').on('click', function(e){
+              //fetch via ajax what is going to be the next link, if we have
+              //>1 links show them to user to choose
+              if(!$('#parent_link').hasClass('disabled')){
+                  $.ajax({
+                    url: '${h.url('changeset_parents',repo_name=c.repo_name, revision=c.changeset.raw_id)}',
+                    success: function(data) {
+                      if(data.results.length === 0){
+                          $('#parent_link').addClass('disabled');
+                          $('#parent_link').html('${_('no revisions')}');
+                      }
+                      if(data.results.length === 1){
+                          var commit = data.results[0];
+                          window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
+                      }
+                      else if(data.results.length === 2){
+                          $('#parent_link').addClass('disabled');
+                          $('#parent_link').addClass('double');
+                          var _html = '';
+                          _html +='<i style="color:#036185" class="icon-chevron-left"></i> <a title="__title__" href="__url__">__rev__</a>'
+                                  .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
+                                  .replace('__title__', data.results[0].message)
+                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
+                          _html +='<br/>'
+                          _html +='<i style="color:#036185" class="icon-chevron-left"></i> <a title="__title__" href="__url__">__rev__</a>'
+                                  .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
+                                  .replace('__title__', data.results[1].message)
+                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
+                          $('#parent_link').html(_html);
+                      }
+                    },
+                  });
+              e.preventDefault();
+              }
+          })
+
       })
 
     </script>
--- a/rhodecode/templates/changeset/changeset_file_comment.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/changeset/changeset_file_comment.html	Wed Jul 02 19:03:13 2014 -0400
@@ -44,8 +44,8 @@
         %endif
 
       <a class="permalink" href="#comment-${co.comment_id}">&para;</a>
-      %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
-          <div onClick="deleteComment(${co.comment_id})" class="buttons delete-comment ui-btn small">${_('Delete')}</div>
+      %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or co.author.user_id == c.rhodecode_user.user_id:
+          <div onClick="deleteComment(${co.comment_id})" class="buttons delete-comment btn btn-mini">${_('Delete')}</div>
       %endif
       </div>
       <div class="text">
@@ -70,7 +70,7 @@
                )
             )|n
            }
-          <div id="preview-btn_{1}" class="preview-btn ui-btn small">${_('Preview')}</div>
+          <div id="preview-btn_{1}" class="preview-btn btn btn-mini">${_('Preview')}</div>
           </div>
             <div class="mentions-container" id="mentions_container_{1}"></div>
             <textarea id="text_{1}" name="text" class="comment-block-ta yui-ac-input"></textarea>
@@ -78,15 +78,15 @@
       <div id="preview-container_{1}" class="clearfix" style="display:none">
          <div class="comment-help">
               ${_('Comment preview')}
-            <div id="edit-btn_{1}" class="edit-btn ui-btn small">${_('Edit')}</div>
+            <div id="edit-btn_{1}" class="edit-btn btn btn-mini">${_('Edit')}</div>
           </div>
           <div id="preview-box_{1}" class="preview-box"></div>
       </div>
       <div class="comment-button">
       <input type="hidden" name="f_path" value="{0}">
       <input type="hidden" name="line" value="{1}">
-      ${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
-      ${h.reset('hide-inline-form', _('Cancel'), class_='ui-btn hide-inline-form')}
+      ${h.submit('save', _('Comment'), class_='btn btn-small save-inline-form')}
+      ${h.reset('hide-inline-form', _('Cancel'), class_='btn btn-small hide-inline-form')}
       </div>
       ${h.end_form()}
   %else:
@@ -97,7 +97,7 @@
           </div>
       </div>
       <div class="comment-button">
-      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
+      ${h.reset('hide-inline-form', _('Hide'), class_='btn btn-small hide-inline-form')}
       </div>
       ${h.end_form()}
   %endif
@@ -163,13 +163,14 @@
                   </a>
                   <input id="show_changeset_status_box" type="checkbox" name="change_changeset_status" style="display: none;" />
               %endif
-              <div id="preview-btn" class="preview-btn ui-btn small">${_('Preview')}</div>
+              <div id="preview-btn" class="preview-btn btn btn-mini">${_('Preview')}</div>
             </div>
             %if change_status:
             <div id="status_block_container" class="status-block" style="display:none">
                 %for status,lbl in c.changeset_statuses:
                     <div class="">
-                        <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" class="status_change_radio" name="changeset_status" id="${status}" value="${status}">
+                        <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" />
+                        <input ${'checked="checked"' if status == cur_status else ''}" type="radio" class="status_change_radio" name="changeset_status" id="${status}" value="${status}">
                         <label for="${status}">${lbl}</label>
                     </div>
                 %endfor
@@ -186,13 +187,13 @@
         <div id="preview-container" class="clearfix" style="display:none">
            <div class="comment-help">
                 ${_('Comment preview')}
-              <div id="edit-btn" class="edit-btn ui-btn small">${_('Edit')}</div>
+              <div id="edit-btn" class="edit-btn btn btn-mini">${_('Edit')}</div>
             </div>
             <div id="preview-box" class="preview-box"></div>
         </div>
 
         <div class="comment-button">
-        ${h.submit('save', _('Comment'), class_="ui-btn large")}
+        ${h.submit('save', _('Comment'), class_="btn")}
         </div>
         ${h.end_form()}
     </div>
--- a/rhodecode/templates/changeset/changeset_range.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/changeset/changeset_range.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,11 +2,17 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Changesets') % c.repo_name} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -&gt; r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)} &middot; ${c.rhodecode_name}
+    ${_('%s Changesets') % c.repo_name} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} &gt; r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
-    ${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -&gt; r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
+    ${_('Changesets')} -
+    r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)}
+    <i class="icon-arrow-right"></i>
+    r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
 </%def>
 
 <%def name="page_nav()">
@@ -22,11 +28,12 @@
     </div>
     <div class="table">
         <div id="body" class="diffblock">
-            <div class="code-header">
+            <div class="code-header" style="height: 26px">
                 <div>
-                ${h.link_to('r%s:%s -> r%s:%s' % (c.cs_ranges[0].revision, h.short_id(c.cs_ranges[0].raw_id), c.cs_ranges[-1].revision, h.short_id(c.cs_ranges[-1].raw_id)),
-                    h.url('compare_url',repo_name=c.repo_name,org_ref_type='rev',org_ref=getattr(c.cs_ranges[0].parents[0] if c.cs_ranges[0].parents else h.EmptyChangeset(),'raw_id'),other_ref_type='rev',other_ref=c.cs_ranges[-1].raw_id)
-                    )}
+                    r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)}
+                    <i class="icon-arrow-right"></i>
+                    r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
+                    <a style="font-weight: bold" href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='rev',org_ref=getattr(c.cs_ranges[0].parents[0] if c.cs_ranges[0].parents else h.EmptyChangeset(),'raw_id'),other_ref_type='rev',other_ref=c.cs_ranges[-1].raw_id)}" class="btn btn-small"><i class="icon-loop"></i> Compare Revisions</a>
                 </div>
             </div>
         </div>
--- a/rhodecode/templates/changeset/diff_block.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/changeset/diff_block.html	Wed Jul 02 19:03:13 2014 -0400
@@ -18,10 +18,18 @@
                     revision=cs2,f_path=h.safe_unicode(path)))}
                 </div>
                 <div class="diff-actions">
-                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full diff for this file'))}"><img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/></a>
-                  <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full side-by-side diff for this file'))}"><img class="icon" src="${h.url('/images/icons/application_double.png')}"/></a>
-                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='raw')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
-                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}"><img class="icon" src="${h.url('/images/icons/page_save.png')}"/></a>
+                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full diff for this file'))}">
+                      <img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/>
+                  </a>
+                  <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full side-by-side diff for this file'))}">
+                      <img class="icon" src="${h.url('/images/icons/application_double.png')}"/>
+                  </a>
+                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='raw')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
+                      <img class="icon" src="${h.url('/images/icons/page_white.png')}"/>
+                  </a>
+                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
+                      <img class="icon" src="${h.url('/images/icons/page_save.png')}"/>
+                  </a>
                   ${c.ignorews_url(request.GET, h.FID(cs2,path))}
                   ${c.context_url(request.GET, h.FID(cs2,path))}
                 </div>
@@ -51,9 +59,17 @@
           <div class="changeset_header">
               <div class="changeset_file">
                   ${h.safe_unicode(filenode_path)} |
-                  <a class="spantag" href="${h.url('files_home', repo_name=c.other_repo.repo_name, f_path=filenode_path, revision=c.org_ref)}" title="${_('Show file at latest version in this repo')}">${c.org_ref_type}@${h.short_id(c.org_ref) if c.org_ref_type=='rev' else c.org_ref}</a> -&gt;
+                  <a class="spantag" href="${h.url('files_home', repo_name=c.other_repo.repo_name, f_path=filenode_path, revision=c.org_ref)}" title="${_('Show file at latest version in this repo')}">${c.org_ref_type}@${h.short_id(c.org_ref) if c.org_ref_type=='rev' else c.org_ref}</a>
+                  <i class="icon-arrow-right" style="font-size: 12px;color:#336699"></i>
                   <a class="spantag" href="${h.url('files_home', repo_name=c.repo_name, f_path=filenode_path, revision=c.other_ref)}" title="${_('Show file at initial version in this repo')}">${c.other_ref_type}@${h.short_id(c.other_ref) if c.other_ref_type=='rev' else c.other_ref}</a>
               </div>
+               <div class="diff-actions">
+                %if c.other_repo.repo_name == c.repo_name:
+                  <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode_path),diff2=c.other_ref,diff1=c.org_ref,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full side-by-side diff for this file'))}">
+                      <img class="icon" src="${h.url('/images/icons/application_double.png')}"/>
+                  </a>
+                %endif
+                </div>
           </div>
       </div>
         <div class="code-body">
--- a/rhodecode/templates/compare/compare_cs.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/compare/compare_cs.html	Wed Jul 02 19:03:13 2014 -0400
@@ -3,16 +3,25 @@
   %if not c.cs_ranges:
     <span class="empty_data">${_('No changesets')}</span>
   %else:
+
+    %if c.ancestor:
+    <div class="ancestor">${_('Ancestor')}:
+      ${h.link_to(h.short_id(c.ancestor),h.url('changeset_home',repo_name=c.repo_name,revision=c.ancestor))}
+    </div>
+    %endif
+
     <table class="compare_view_commits noborder">
     %for cs in reversed(c.cs_ranges):
-        <tr>
+        <tr id="row-${cs.raw_id}">
         <td>
           %if cs.raw_id in c.statuses:
-            <div title="${_('Changeset status: %s') % c.statuses[cs.raw_id][1]}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cs.raw_id][0])}" /></div>
+            <div title="${_('Changeset status: %s') % c.statuses[cs.raw_id][1]}" class="changeset-status-ico">
+                <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cs.raw_id][0])}" />
+            </div>
           %endif
         </td>
-        <td><span class="tooltip" title="${h.tooltip(h.age(cs.date))}">${cs.date}</span></td>
-        <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),14)}"/></div></td>
+        <td style="min-width: 120px"><span class="tooltip" title="${h.tooltip(h.age(cs.date))}">${cs.date}</span></td>
+        <td><div class="gravatar" commit_id="${cs.raw_id}"><img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),14)}"/></div></td>
         <td><div class="author">${h.person(cs.author)}</div></td>
         <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.other_repo.repo_name,revision=cs.raw_id))}
         %if c.as_form:
@@ -24,18 +33,28 @@
         <span class="branchtag">${cs.branch}</span>
         %endif
         </td>
-        <td><div class="message tooltip" title="${h.tooltip(cs.message)}" style="white-space:normal">${h.urlify_commit(h.shorter(cs.message, 60),c.repo_name)}</div></td>
+        <td class="expand_commit" commit_id="${cs.raw_id}" title="${_('Expand commit message')}">
+            <i class="icon-resize-vertical" style="color:#DDD"></i>
+        </td>
+        <td><div id="C-${cs.raw_id}" class="message" style="white-space:normal; height: 10px;width: 250px">${h.urlify_commit(cs.message, c.repo_name)}</div></td>
         </tr>
     %endfor
     </table>
-    %if c.ancestor:
-    <span class="ancestor">${_('Ancestor')}:
-      ${h.link_to(h.short_id(c.ancestor),h.url('changeset_home',repo_name=c.repo_name,revision=c.ancestor))}
-    </span>
-    %endif
     %if c.as_form:
       ${h.hidden('ancestor_rev',c.ancestor)}
       ${h.hidden('merge_rev',c.cs_ranges[-1].raw_id)}
     %endif
   %endif
 </div>
+
+<script>
+$('.expand_commit').on('click',function(e){
+    $(this).children('i').hide();
+    var cid = $(this).attr('commit_id');
+    $('#C-'+cid).css({'height': 'auto','width': 'auto', 'white-space': 'pre'})
+});
+$('.gravatar').on('click',function(e){
+    var cid = $(this).attr('commit_id');
+    $('#row-'+cid).toggleClass('hl', !$('#row-'+cid).hasClass('hl'));
+});
+</script>
--- a/rhodecode/templates/compare/compare_diff.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/compare/compare_diff.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,14 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -&gt; ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} &middot; ${c.rhodecode_name}
+    %if c.compare_home:
+        ${_('%s Compare') % c.repo_name}
+    %else:
+        ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} &gt; ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)}
+    %endif
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -22,68 +29,161 @@
     </div>
     <div class="table">
         <div id="body" class="diffblock">
-            <div class="code-header">
+            <div class="code-header" style="height: 26px">
                 <div>
-                ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -&gt; ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)}  <a href="${c.swap_url}">[swap]</a>
+                    ${h.hidden('compare_org')} <i class="icon-ellipsis-horizontal" style="color: #999; vertical-align: -12px; padding: 0px 0px 0px 2px"></i> ${h.hidden('compare_other')}
+                    %if not c.compare_home:
+                        <a class="btn btn-small" href="${c.swap_url}"><i class="icon-refresh"></i> ${_('Swap')}</a>
+                    %endif
+                    <div id="compare_revs" class="btn btn-small"><i class="icon-loop"></i> ${_('Compare Revisions')}</div>
                 </div>
             </div>
         </div>
-        <div id="changeset_compare_view_content">
-            ##CS
-            <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div>
-            <%include file="compare_cs.html" />
 
-            ## FILES
-            <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
+    %if c.compare_home:
+        <div id="changeset_compare_view_content">
+         <div style="color:#999;font-size: 18px">${_('Compare revisions, branches, bookmarks or tags.')}</div>
+        </div>
+    %else:
+        <div id="changeset_compare_view_content">
+                ##CS
+                <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div>
+                <%include file="compare_cs.html" />
 
-            % if c.limited_diff:
-                ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)}
-            % else:
-                ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}:
-            %endif
+                ## FILES
+                <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
+
+                % if c.limited_diff:
+                    ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)}
+                % else:
+                    ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}:
+                %endif
 
-            </div>
-            <div class="cs_files">
-              %if not c.files:
-                 <span class="empty_data">${_('No files')}</span>
-              %endif
-              %for fid, change, f, stat in c.files:
-                  <div class="cs_${change}">
-                    <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid, **request.GET.mixed()))}</div>
-                    <div class="changes">${h.fancy_file_stats(stat)}</div>
-                  </div>
-              %endfor
-            </div>
-            % if c.limited_diff:
-              <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff')}</a></h5>
-            % endif
-        </div>
+                </div>
+                <div class="cs_files">
+                  %if not c.files:
+                     <span class="empty_data">${_('No files')}</span>
+                  %endif
+                  %for fid, change, f, stat in c.files:
+                      <div class="cs_${change}">
+                        <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid, **request.GET.mixed()))}</div>
+                        <div class="changes">${h.fancy_file_stats(stat)}</div>
+                      </div>
+                  %endfor
+                </div>
+                % if c.limited_diff:
+                  <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff')}</a></h5>
+                % endif
+         </div>
+
+        ## diff block
+        <%namespace name="diff_block" file="/changeset/diff_block.html"/>
+        %for fid, change, f, stat in c.files:
+          ${diff_block.diff_block_simple([c.changes[fid]])}
+        %endfor
+        % if c.limited_diff:
+          <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff')}</a></h4>
+        % endif
+    %endif
     </div>
 
-    ## diff block
-    <%namespace name="diff_block" file="/changeset/diff_block.html"/>
-    %for fid, change, f, stat in c.files:
-      ${diff_block.diff_block_simple([c.changes[fid]])}
-    %endfor
-    % if c.limited_diff:
-      <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff')}</a></h4>
-    % endif
-     <script type="text/javascript">
-
-      YUE.onDOMReady(function(){
+</div>
+    <script type="text/javascript">
 
-          YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
-              var act = e.currentTarget.nextElementSibling;
+   $(document).ready(function(){
+    var cache = {}
+    $("#compare_org").select2({
+        placeholder: "${'%s@%s' % (c.org_repo.repo_name, c.org_ref)}",
+        formatSelection: function(obj){
+            return '{0}@{1}'.format("${c.org_repo.repo_name}", obj.text)
+        },
+        dropdownAutoWidth: true,
+        query: function(query){
+          var key = 'cache';
+          var cached = cache[key] ;
+          if(cached) {
+            var data = {results: []};
+            //filter results
+            $.each(cached.results, function(){
+                var section = this.text;
+                var children = [];
+                $.each(this.children, function(){
+                    if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
+                        children.push({'id': this.id, 'text': this.text})
+                    }
+                })
+                data.results.push({'text': section, 'children': children})
+            });
+            //push the typed in changeset
+            data.results.push({'text':_TM['specify changeset'],
+                               'children': [{'id': query.term, 'text': query.term, 'type': 'rev'}]})
+            query.callback(data);
+          }else{
+              $.ajax({
+                url: pyroutes.url('repo_refs_data', {'repo_name': '${c.org_repo.repo_name}'}),
+                data: {},
+                dataType: 'json',
+                type: 'GET',
+                success: function(data) {
+                  cache[key] = data;
+                  query.callback({results: data.results});
+                }
+              })
+          }
+        },
+    });
+    $("#compare_other").select2({
+        placeholder: "${'%s@%s' % (c.other_repo.repo_name, c.other_ref)}",
+        dropdownAutoWidth: true,
+        formatSelection: function(obj){
+            return '{0}@{1}'.format("${c.other_repo.repo_name}", obj.text)
+        },
+        query: function(query){
+          var key = 'cache2';
+          var cached = cache[key] ;
+          if(cached) {
+            var data = {results: []};
+            //filter results
+            $.each(cached.results, function(){
+                var section = this.text;
+                var children = [];
+                $.each(this.children, function(){
+                    if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
+                        children.push({'id': this.id, 'text': this.text})
+                    }
+                })
+                data.results.push({'text': section, 'children': children})
+            });
+            //push the typed in changeset
+            data.results.push({'text':_TM['specify changeset'],
+                               'children': [{'id': query.term, 'text': query.term, 'type': 'rev'}]})
+            query.callback(data);
+          }else{
+              $.ajax({
+                url: pyroutes.url('repo_refs_data', {'repo_name': '${c.other_repo.repo_name}'}),
+                data: {},
+                dataType: 'json',
+                type: 'GET',
+                success: function(data) {
+                  cache[key] = data;
+                  query.callback({results: data.results});
+                }
+              })
+          }
+        },
+    });
 
-              if(YUD.hasClass(act,'active')){
-                  YUD.removeClass(act,'active');
-                  YUD.setStyle(act,'display','none');
-              }else{
-                  YUD.addClass(act,'active');
-                  YUD.setStyle(act,'display','');
-              }
-          });
-      })
+    $('#compare_revs').on('click', function(e){
+        var org = $('#compare_org').select2('data');
+        var other = $('#compare_other').select2('data');
+
+        var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='__other_ref_type__',org_ref='__org__',other_ref_type='__org_ref_type__',other_ref='__other__', other_repo=c.other_repo.repo_name)}";
+        var u = compare_url.replace('__other_ref_type__',org.type)
+                           .replace('__org__',org.text)
+                           .replace('__org_ref_type__',other.type)
+                           .replace('__other__',other.text);
+        window.location=u;
+    })
+   });
     </script>
-    </div>
 </%def>
--- a/rhodecode/templates/data_table/_dt_elements.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/data_table/_dt_elements.html	Wed Jul 02 19:03:13 2014 -0400
@@ -4,11 +4,13 @@
 
 <%def name="quick_menu(repo_name)">
   <ul class="menu_items hidden">
+  ##<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu">
+
     <li style="border-top:1px solid #003367;margin-left:18px;padding-left:-99px"></li>
     <li>
        <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
        <span class="icon">
-           <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
+           <i class="icon-file-text"></i>
        </span>
        <span>${_('Summary')}</span>
        </a>
@@ -16,7 +18,7 @@
     <li>
        <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
        <span class="icon">
-           <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
+           <i class="icon-list-alt"></i>
        </span>
        <span>${_('Changelog')}</span>
        </a>
@@ -24,7 +26,7 @@
     <li>
        <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
        <span class="icon">
-           <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
+           <i class="icon-file-alt"></i>
        </span>
        <span>${_('Files')}</span>
        </a>
@@ -32,7 +34,7 @@
     <li>
        <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
        <span class="icon">
-           <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Fork')}" />
+           <i class="icon-code-fork"></i>
        </span>
        <span>${_('Fork')}</span>
        </a>
@@ -40,7 +42,7 @@
   </ul>
 </%def>
 
-<%def name="repo_name(name,rtype,private,fork_of,short_name=False,admin=False)">
+<%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
     <%
     def get_name(name,short_name=short_name):
       if short_name:
@@ -48,31 +50,37 @@
       else:
         return name
     %>
-  <div style="white-space: nowrap">
-   ##TYPE OF REPO
-   %if h.is_hg(rtype):
-     <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
-   %elif h.is_git(rtype):
-     <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
-   %endif
+  <div style="white-space: nowrap; ${'opacity: 0.5' if rstate == 'repo_state_pending' else ''}}">
+    ##NAME
+    %if admin:
+        <a href="${h.url('edit_repo',repo_name=name)}">
+    %else:
+        <a href="${h.url('summary_home',repo_name=name)}">
+    %endif
+
+    ##TYPE OF REPO
+    %if h.is_hg(rtype):
+        <span title="${_('Mercurial repository')}"><i class="icon-hg" style="color: #316293; font-size: 14px;"></i></span>
+    %elif h.is_git(rtype):
+        <span title="${_('Git repository')}"><i class="icon-git" style="color: #e85634; font-size: 14px;"></i></span>
+    %endif
 
-   ##PRIVATE/PUBLIC
-   %if private and c.visual.show_private_icon:
-     <img class="icon" title="${_('Private repository')}" alt="${_('Private repository')}" src="${h.url('/images/icons/private_repo.png')}"/>
-   %elif not private and c.visual.show_public_icon:
-     <img class="icon" title="${_('Public repository')}" alt="${_('Public repository')}" src="${h.url('/images/icons/public_repo.png')}"/>
-   %endif
-
-   ##NAME
-   %if admin:
-    ${h.link_to(get_name(name),h.url('edit_repo',repo_name=name),class_="repo_name")}
-   %else:
-    ${h.link_to(get_name(name),h.url('summary_home',repo_name=name),class_="repo_name")}
-   %endif
-   %if fork_of:
-        <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}">
-        <img class="icon" alt="${_('Fork')}" title="${_('Fork of %s') % fork_of.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/></a>
-   %endif
+    ##PRIVATE/PUBLIC
+    %if private and c.visual.show_private_icon:
+      <i class="icon-lock" style="color: #e85634; font-size: 16px; vertical-align: -2px; margin: 0px 1px 0px 3px" title="${_('Private repository')}"></i>
+    %elif not private and c.visual.show_public_icon:
+      <i class="icon-unlock-alt" style="color: #999999; font-size: 16px; vertical-align: -2px; margin: 0px 1px 0px 3px" title="${_('Public repository')}"></i>
+    %else:
+      <span style="margin: 0px 8px 0px 8px"></span>
+    %endif
+    ${get_name(name)}
+    </a>
+    %if fork_of:
+      <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
+    %endif
+    %if rstate == 'repo_state_pending':
+      <i class="icon-cogs" style="color: #036185;" title="${_('Repository creating in progress...')}"></i>
+    %endif
   </div>
 </%def>
 
@@ -92,17 +100,17 @@
 
 <%def name="rss(name)">
   %if c.rhodecode_user.username != 'default':
-    <a title="${_('Subscribe to %s rss feed')% name}" class="rss_icon"  href="${h.url('rss_feed_home',repo_name=name,api_key=c.rhodecode_user.api_key)}"></a>
+    <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,api_key=c.rhodecode_user.api_key)}"><i class="icon-rss-sign" style="color: #fa9b39"></i></a>
   %else:
-    <a title="${_('Subscribe to %s rss feed')% name}" class="rss_icon"  href="${h.url('rss_feed_home',repo_name=name)}"></a>
+    <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-sign" style="color: #fa9b39"></i></a>
   %endif
 </%def>
 
 <%def name="atom(name)">
   %if c.rhodecode_user.username != 'default':
-    <a title="${_('Subscribe to %s atom feed')% name}"  class="atom_icon" href="${h.url('atom_feed_home',repo_name=name,api_key=c.rhodecode_user.api_key)}"></a>
+    <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,api_key=c.rhodecode_user.api_key)}"><i class="icon-rss-sign"  style="color: #fa9b39"></i></a>
   %else:
-    <a title="${_('Subscribe to %s atom feed')% name}"  class="atom_icon" href="${h.url('atom_feed_home',repo_name=name)}"></a>
+    <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-sign"  style="color: #fa9b39"></i></a>
   %endif
 </%def>
 
@@ -112,37 +120,98 @@
 
 <%def name="repo_actions(repo_name, super_user=True)">
   <div>
-    <div style="float:left">
+    <div style="float:left; margin-right:5px;" class="grid_edit">
       <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('edit')}">
-        ${h.submit('edit_%s' % repo_name,_('edit'),class_="edit_icon action_button")}
+        <i class="icon-pencil"></i> ${h.submit('edit_%s' % repo_name,_('edit'),class_="action_button")}
       </a>
     </div>
-    <div style="float:left">
+    <div style="float:left" class="grid_delete">
       ${h.form(h.url('repo', repo_name=repo_name),method='delete')}
-        ${h.submit('remove_%s' % repo_name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
+        <i class="icon-remove-sign" style="color:#FF4444"></i>
+        ${h.submit('remove_%s' % repo_name,_('delete'),class_="action_button",
+        onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
       ${h.end_form()}
     </div>
   </div>
 </%def>
 
+<%def name="repo_state(repo_state)">
+  <div>
+    %if repo_state == 'repo_state_pending':
+        <div class="btn btn-mini btn-info disabled">${_('Creating')}</div>
+    %elif repo_state == 'repo_state_created':
+        <div class="btn btn-mini btn-success disabled">${_('Created')}</div>
+    %else:
+        <div class="btn btn-mini btn-danger disabled" title="${repo_state}">invalid</div>
+    %endif
+  </div>
+</%def>
+
 <%def name="user_actions(user_id, username)">
- <div style="float:left">
+ <div style="float:left" class="grid_edit">
    <a href="${h.url('edit_user',id=user_id)}" title="${_('edit')}">
-     ${h.submit('edit_%s' % username,_('edit'),class_="edit_icon action_button")}
+     <i class="icon-pencil"></i> ${h.submit('edit_%s' % username,_('edit'),class_="action_button")}
    </a>
  </div>
- <div style="float:left">
+ <div style="float:left" class="grid_delete">
   ${h.form(h.url('delete_user', id=user_id),method='delete')}
-      ${h.submit('remove_',_('delete'),id="remove_user_%s" % user_id,
-      class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
+    <i class="icon-remove-sign" style="color:#FF4444"></i>
+    ${h.submit('remove_',_('delete'),id="remove_user_%s" % user_id, class_="action_button",
+    onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
   ${h.end_form()}
  </div>
 </%def>
 
+<%def name="user_group_actions(user_group_id, user_group_name)">
+ <div style="float:left" class="grid_edit">
+    <a href="${h.url('edit_users_group', id=user_group_id)}" title="${_('Edit')}">
+    <i class="icon-pencil"></i>
+     ${h.submit('edit_%s' % user_group_name,_('edit'),class_="action_button", id_="submit_user_group_edit")}
+    </a>
+ </div>
+ <div style="float:left" class="grid_delete">
+    ${h.form(h.url('users_group', id=user_group_id),method='delete')}
+      <i class="icon-remove-sign" style="color:#FF4444"></i>
+      ${h.submit('remove_',_('delete'),id="remove_group_%s" % user_group_id, class_="action_button",
+      onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
+    ${h.end_form()}
+ </div>
+</%def>
+
+<%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
+ <div style="float:left" class="grid_edit">
+    <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">
+    <i class="icon-pencil"></i>
+     ${h.submit('edit_%s' % repo_group_name, _('edit'),class_="action_button")}
+    </a>
+ </div>
+ <div style="float:left" class="grid_delete">
+    ${h.form(h.url('repos_group', group_name=repo_group_name),method='delete')}
+        <i class="icon-remove-sign" style="color:#FF4444"></i>
+        ${h.submit('remove_%s' % repo_group_name,_('delete'),class_="action_button",
+        onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
+    ${h.end_form()}
+ </div>
+</%def>
+
 <%def name="user_name(user_id, username)">
     ${h.link_to(username,h.url('edit_user', id=user_id))}
 </%def>
 
+<%def name="repo_group_name(repo_group_name, children_groups)">
+  <div style="white-space: nowrap">
+  <a href="${h.url('repos_group_home',group_name=repo_group_name)}">
+    <i class="icon-folder-close" title="${_('Repository group')}"></i> ${h.literal(' &raquo; '.join(children_groups))}</a>
+  </div>
+</%def>
+
+<%def name="user_group_name(user_group_id, user_group_name)">
+  <div style="white-space: nowrap">
+  <a href="${h.url('edit_users_group', id=user_group_id)}">
+    <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
+  </div>
+</%def>
+
 <%def name="toggle_follow(repo_id)">
   <span id="follow_toggle_${repo_id}" class="following" title="${_('Stop following this repository')}"
         onclick="javascript:toggleFollowingRepo(this, ${repo_id},'${str(h.get_token())}')">
--- a/rhodecode/templates/errors/error_document.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/errors/error_document.html	Wed Jul 02 19:03:13 2014 -0400
@@ -13,7 +13,8 @@
         %endif
 
         <!-- stylesheets -->
-        <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
+        <link rel="stylesheet" type="text/css" href="${h.url('/css/newstyle.css')}" media="screen"/>
+        <link rel="stylesheet" type="text/css" href="${h.url('/css/bootstrap.css')}" media="screen"/>
         <style type="text/css">
          #main_div{
            border: 0px solid #000;
@@ -33,16 +34,7 @@
 
     </head>
     <body>
-        <div class="flash_msg">
-            <% messages = h.flash.pop_messages() %>
-            % if messages:
-            <ul id="flash-messages">
-                % for message in messages:
-                <li class="${message.category}_msg">${message}</li>
-                % endfor
-            </ul>
-            % endif
-        </div>
+        <%include file="/base/flash_msg.html"/>
         <div id="login">
             <div class="table">
                 <div id="main_div">
--- a/rhodecode/templates/files/diff_2way.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/files/diff_2way.html	Wed Jul 02 19:03:13 2014 -0400
@@ -3,7 +3,6 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="js_extra()">
-<script type="text/javascript" src="${h.url('/js/jquery.1.10.1.min.js')}"></script>
 <script type="text/javascript" src="${h.url('/js/codemirror.js')}"></script>
 <script type="text/javascript" src="${h.url('/js/mergerly.js')}"></script>
 </%def>
@@ -13,7 +12,10 @@
 </%def>
 
 <%def name="title()">
-    ${_('%s File side-by-side diff') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s File side-by-side diff') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -41,10 +43,18 @@
                         revision=c.cs2.raw_id,f_path=h.safe_unicode(c.node1.path)))}
                     </div>
                     <div class="diff-actions">
-                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full diff for this file'))}"><img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/></a>
-                      <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full side-by-side diff for this file'))}"><img class="icon" src="${h.url('/images/icons/application_double.png')}"/></a>
-                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='raw')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
-                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}"><img class="icon" src="${h.url('/images/icons/page_save.png')}"/></a>
+                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full diff for this file'))}">
+                          <img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/>
+                      </a>
+                      <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full side-by-side diff for this file'))}">
+                          <img class="icon" src="${h.url('/images/icons/application_double.png')}"/>
+                      </a>
+                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='raw')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
+                          <img class="icon" src="${h.url('/images/icons/page_white.png')}"/>
+                      </a>
+                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
+                          <img class="icon" src="${h.url('/images/icons/page_save.png')}"/>
+                      </a>
                       ${h.checkbox('ignorews', label=_('ignore white space'))}
                       ${h.checkbox('edit_mode', label=_('turn on edit mode'))}
                     </div>
@@ -55,8 +65,8 @@
     </div>
 
 <script>
-var orig1 = '${(c.orig1)|n}';
-var orig2 = '${(c.orig2)|n}';
+var orig1_url = '${h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),revision=c.cs1.raw_id)}';
+var orig2_url = '${h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node2.path),revision=c.cs2.raw_id)}';
 
 $(document).ready(function () {
     $('#compare').mergely({
@@ -67,10 +77,21 @@
         viewport: true,
         cmsettings: {mode: 'text/plain', readOnly: true, lineWrapping: false, lineNumbers: true},
         lhs: function(setValue) {
-            setValue(orig1);
+            if("${c.node1.is_binary}" == "True"){
+                setValue('Binary file')
+            }
+            else{
+                $.ajax(orig1_url, {dataType: 'text', success: setValue});
+            }
+
         },
         rhs: function(setValue) {
-            setValue(orig2);
+            if("${c.node2.is_binary}" == "True"){
+                setValue('Binary file')
+            }
+            else{
+                $.ajax(orig2_url, {dataType: 'text', success: setValue});
+            }
         },
     });
     $('#ignorews').change(function(e){
--- a/rhodecode/templates/files/file_diff.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/files/file_diff.html	Wed Jul 02 19:03:13 2014 -0400
@@ -1,7 +1,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s File Diff') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s File Diff') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
--- a/rhodecode/templates/files/files.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/files/files.html	Wed Jul 02 19:03:13 2014 -0400
@@ -5,7 +5,10 @@
     %if hasattr(c,'file'):
         &middot; ${h.safe_unicode(c.file.path) or '\\'}
     %endif
-    &middot; ${c.rhodecode_name}
+
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -48,6 +51,11 @@
 // send the node history requst to this url
 var node_history_url = '${h.url("files_history_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
 
+## new pyroutes URLs
+pyroutes.register('files_nodelist_home', "${h.url('files_nodelist_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s')}", ['revision', 'f_path']);
+pyroutes.register('files_history_home', "${h.url('files_history_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s')}", ['revision', 'f_path']);
+pyroutes.register('files_authors_home', "${h.url('files_authors_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s')}", ['revision', 'f_path']);
+
 var ypjax_links = function(){
     YUE.on(YUQ('.ypjax-link'), 'click',function(e){
 
@@ -89,29 +97,104 @@
     });
 }
 
+// callbacks needed to process the pjax filebrowser
 var callbacks = function(State){
     ypjax_links();
     tooltip_activate();
-    fileBrowserListeners(State.url, State.data.node_list_url, State.data.url_base);
+
+    if(State !== undefined){
+        //inistially loaded stuff
+        var _f_path = State.data.f_path;
+        var _rev = State.data.rev;
+
+        fileBrowserListeners(State.url, State.data.node_list_url, State.data.url_base);
+        // Inform Google Analytics of the change
+        if ( typeof window.pageTracker !== 'undefined' ) {
+            window.pageTracker._trackPageview(State.url);
+        }
+    }
+
+    function highlight_lines(lines){
+        for(pos in lines){
+          YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
+        }
+    }
+    page_highlights = location.href.substring(location.href.indexOf('#')+1).split('L');
+    if (page_highlights.length == 2){
+       highlight_ranges  = page_highlights[1].split(",");
 
-    if(YUD.get('hlcode')){
-        YUE.on('hlcode', 'mouseup', getSelectionLink);
+       var h_lines = [];
+       for (pos in highlight_ranges){
+            var _range = highlight_ranges[pos].split('-');
+            if(_range.length == 2){
+                var start = parseInt(_range[0]);
+                var end = parseInt(_range[1]);
+                if (start < end){
+                    for(var i=start;i<=end;i++){
+                        h_lines.push(i);
+                    }
+                }
+            }
+            else{
+                h_lines.push(parseInt(highlight_ranges[pos]));
+            }
+      }
+      highlight_lines(h_lines);
+      var _first_line= YUD.get('L'+h_lines[0]);
+      if(_first_line){
+          _first_line.scrollIntoView()
+      }
     }
-    //console.log(State);
-    if(YUD.get('load_node_history')){
-      //remove all listeners due to problems of history state
-      YUE.removeListener('load_node_history', 'click');
-      YUE.on('load_node_history', 'click', function(e){
-          var _url = node_history_url.replace('__REV__',State.data.rev).replace('__FPATH__', State.data.f_path);
-          ypjax(_url, 'node_history', function(o){
-              tooltip_activate();
-          })
-      });
-    }
-    // Inform Google Analytics of the change
-    if ( typeof window.pageTracker !== 'undefined' ) {
-        window.pageTracker._trackPageview(State.url);
-    }
+
+    // select code link event
+    YUE.on('hlcode', 'mouseup', getSelectionLink);
+
+    // history select field
+    var cache = {}
+    $("#diff1").select2({
+        placeholder: _TM['Select changeset'],
+        dropdownAutoWidth: true,
+        query: function(query){
+          var key = 'cache';
+          var cached = cache[key] ;
+          if(cached) {
+            var data = {results: []};
+            //filter results
+            $.each(cached.results, function(){
+                var section = this.text;
+                var children = [];
+                $.each(this.children, function(){
+                    if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
+                        children.push({'id': this.id, 'text': this.text})
+                    }
+                })
+                data.results.push({'text': section, 'children': children})
+            });
+            query.callback(data);
+          }else{
+              $.ajax({
+                url: pyroutes.url('files_history_home', {'revision': _rev, 'f_path': _f_path}),
+                data: {},
+                dataType: 'json',
+                type: 'GET',
+                success: function(data) {
+                  cache[key] = data;
+                  query.callback({results: data.results});
+                }
+              })
+          }
+        },
+    });
+    $('#show_authors').on('click', function(){
+        $.ajax({
+            url: pyroutes.url('files_authors_home', {'revision': _rev, 'f_path': _f_path}),
+            success: function(data) {
+                $('#file_authors').html(data);
+                $('#file_authors').show();
+                tooltip_activate()
+            }
+        })
+    })
 }
 
 YUE.onDOMReady(function(){
--- a/rhodecode/templates/files/files_add.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/files/files_add.html	Wed Jul 02 19:03:13 2014 -0400
@@ -1,13 +1,17 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Files Add') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s Files Add') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="js_extra()">
 <script type="text/javascript" src="${h.url('/js/codemirror.js')}"></script>
 <script type="text/javascript" src="${h.url('/js/codemirror_loadmode.js')}"></script>
 <script type="text/javascript" src="${h.url('/js/mode/meta.js')}"></script>
+<script type="text/javascript" src="${h.url('/js/mode/meta_ext.js')}"></script>
 </%def>
 <%def name="css_extra()">
 <link rel="stylesheet" type="text/css" href="${h.url('/css/codemirror.css')}"/>
@@ -18,7 +22,7 @@
 </%def>
 
 <%def name="breadcrumbs_links()">
-    ${_('Add file')} @ ${h.show_id(c.cs)}
+    ${_('Add new file')} @ ${h.show_id(c.cs)}
 </%def>
 
 <%def name="main()">
@@ -34,43 +38,23 @@
             </li>
         </ul>
     </div>
-    <div class="table">
+    <div class="table" id="edit">
         <div id="files_data">
-          ${h.form(h.url.current(),method='post',id='eform',enctype="multipart/form-data")}
-            <h3>${_('Add new file')}</h3>
-            <div class="form">
-              <div class="fields">
-                  <div id="filename_container" class="field file">
-                      <div class="label">
-                          <label for="filename">${_('File Name')}:</label>
-                      </div>
-                      <div class="input">
-                          <input type="text" value="" size="30" name="filename" id="filename">
-                          ${_('or')} <span class="ui-btn" id="upload_file_enable">${_('Upload file')}</span>
-                      </div>
-                  </div>
-                  <div id="upload_file_container" class="field" style="display:none">
-                    <div class="label">
-                        <label for="upload_file_container">${_('Upload file')}</label>
-                    </div>
-                    <div class="file">
-                        <input type="file"  size="30" name="upload_file" id="upload_file">
-                        ${_('or')} <span class="ui-btn" id="file_enable">${_('Create new file')}</span>
-                    </div>
-                  </div>
-                   <div class="field">
-                      <div class="label">
-                          <label for="location">${_('Location')}</label>
-                      </div>
-                      <div class="input">
-                          <input type="text" value="${c.f_path}" size="30" name="location" id="location">
-                          ${_('use / to separate directories')}
-                      </div>
-                   </div>
-              </div>
-            </div>
+          ${h.form(h.url.current(),method='post',id='eform',enctype="multipart/form-data", class_="form-horizontal")}
+          <h3 class="files_location">
+            ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.raw_id,c.f_path)} /
+              <span id="filename_container" class="file reviewer_ac">
+                  <input class="input-small" type="text" value="" size="30" name="filename" id="filename" placeholder="${_('Enter filename...')}">
+                  <input type="hidden" value="${c.f_path}" size="30" name="location" id="location">
+                  ${_('or')} <div class="btn btn-small" id="upload_file_enable">${_('Upload File')}</div>
+              </span>
+              <span id="upload_file_container" class="reviewer_ac" style="display:none">
+                  <input type="file"  size="20" name="upload_file" id="upload_file">
+                  ${_('or')} <div class="btn btn-small" id="file_enable">${_('Create New File')}</div>
+              </span>
+          </h3>
             <div id="body" class="codeblock">
-            <div class="code-header">
+            <div class="code-header" id="set_mode_header">
                 <label class="commit" for="set_mode">${_('New file mode')}</label>
                 ${h.select('set_mode','plain',[('plain',_('plain'))])}
             </div>
@@ -82,8 +66,8 @@
                 <textarea id="commit" name="message" style="height: 100px;width: 99%;margin-left:4px" placeholder="${c.default_message}"></textarea>
             </div>
             <div style="text-align: left;padding-top: 5px">
-            ${h.submit('commit',_('Commit changes'),class_="ui-btn")}
-            ${h.reset('reset',_('Reset'),class_="ui-btn")}
+            ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")}
+            ${h.reset('reset',_('Reset'),class_="btn btn-small")}
             </div>
             ${h.end_form()}
             <script type="text/javascript">
@@ -91,7 +75,7 @@
             var myCodeMirror = initCodeMirror('editor',reset_url);
             CodeMirror.modeURL = "${h.url('/js/mode/%N/%N.js')}";
 
-            //inject new modes
+            //inject new modes, based on codeMirrors modeInfo object
             var modes_select = YUD.get('set_mode');
             for(var i=0;i<CodeMirror.modeInfo.length;i++){
                 var m = CodeMirror.modeInfo[i];
--- a/rhodecode/templates/files/files_browser.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/files/files_browser.html	Wed Jul 02 19:03:13 2014 -0400
@@ -10,11 +10,10 @@
         <div class="browser-nav">
             ${h.form(h.url.current())}
             <div class="info_box">
-              <span class="rev">${_('View')}@rev</span>
-              <a class="ui-btn ypjax-link" href="${c.url_prev}" title="${_('Previous revision')}">&laquo;</a>
-              ${h.text('at_rev',value=c.changeset.revision,size=5)}
-              <a class="ui-btn ypjax-link" href="${c.url_next}" title="${_('Next revision')}">&raquo;</a>
-              ## ${h.submit('view',_('View'),class_="ui-btn")}
+              <div class="info_box_elem rev">${_('revision')}</div>
+              <div class="info_box_elem"><a class="btn btn-mini ypjax-link" href="${c.url_prev}" title="${_('Previous revision')}"><i class="icon-chevron-left"></i></a></div>
+              <div class="info_box_elem">${h.text('at_rev',value=c.changeset.revision,size=5)}</div>
+              <div class="info_box_elem"><a class="btn btn-mini ypjax-link" href="${c.url_next}" title="${_('Next revision')}"><i class="icon-chevron-right"></i></a></div>
             </div>
             ${h.end_form()}
         </div>
@@ -22,22 +21,17 @@
            ${h.checkbox('stay_at_branch',c.changeset.branch,c.changeset.branch==c.branch)}
            <label>${_('Follow current branch')}</label>
         </div>
+        <div id="search_activate_id" class="search_activate">
+           <a class="btn btn-mini" id="filter_activate" href="#">${_('Search File List')}</a>
+        </div>
         <div class="browser-search">
-              <div id="search_activate_id" class="search_activate">
-                  <a class="ui-btn" id="filter_activate" href="#">${_('Search file list')}</a>
-              </div>
-              % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
-                    <div id="add_node_id" class="add_node">
-                        <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('Add new file')}</a>
-                    </div>
-              % endif
-        <div>
-            <div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
-            <div id="node_filter_box" style="display:none">
-            ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}/<input class="init" type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
+            <div>
+                <div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
+                <div id="node_filter_box" style="display:none">
+                ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}/<input class="init" type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
+                </div>
             </div>
         </div>
-        </div>
     </div>
 
     <div class="browser-body">
@@ -114,3 +108,13 @@
         </table>
     </div>
 </div>
+
+<script>
+    $(document).ready(function(){
+        // init node filter if we pass GET param ?search=1
+        var search_GET = "${request.GET.get('search','')}";
+        if(search_GET == "1"){
+            _NODEFILTER.initFilter();
+        }
+    })
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/files/files_delete.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,64 @@
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('%s Files Delete') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="js_extra()">
+<script type="text/javascript" src="${h.url('/js/codemirror.js')}"></script>
+<script type="text/javascript" src="${h.url('/js/codemirror_loadmode.js')}"></script>
+<script type="text/javascript" src="${h.url('/js/mode/meta.js')}"></script>
+<script type="text/javascript" src="${h.url('/js/mode/meta_ext.js')}"></script>
+</%def>
+<%def name="css_extra()">
+<link rel="stylesheet" type="text/css" href="${h.url('/css/codemirror.css')}"/>
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('repositories')}
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${_('Delete file')} @ ${h.show_id(c.cs)}
+</%def>
+
+<%def name="main()">
+${self.repo_context_bar('files')}
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+        <ul class="links">
+            <li>
+              <span style="text-transform: uppercase;">
+              <a href="#">${_('Branch')}: ${c.cs.branch}</a></span>
+            </li>
+        </ul>
+    </div>
+    <div class="table" id="edit">
+        <div id="files_data">
+            ${h.form(h.url.current(),method='post',class_="form-horizontal")}
+            <h3 class="files_location">
+                ${_('Delete file')}: ${h.files_breadcrumbs(c.repo_name,c.cs.raw_id,c.f_path)}
+            </h3>
+
+            <div id="body" class="codeblock">
+                <div id="editor_container">
+                    <pre id="editor_pre"></pre>
+                    <textarea id="editor" name="content" style="display:none"></textarea>
+                </div>
+                <div style="padding: 10px;color:#666666">${_('Commit message')}</div>
+                <textarea id="commit" name="message" style="height: 100px;width: 99%;margin-left:4px" placeholder="${c.default_message}"></textarea>
+            </div>
+            <div style="text-align: left;padding-top: 5px">
+                ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")}
+                ${h.reset('reset',_('Reset'),class_="btn btn-small")}
+            </div>
+            ${h.end_form()}
+        </div>
+    </div>
+</div>
+</%def>
--- a/rhodecode/templates/files/files_edit.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/files/files_edit.html	Wed Jul 02 19:03:13 2014 -0400
@@ -1,13 +1,17 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Files Edit') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s File Edit') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="js_extra()">
 <script type="text/javascript" src="${h.url('/js/codemirror.js')}"></script>
 <script type="text/javascript" src="${h.url('/js/codemirror_loadmode.js')}"></script>
 <script type="text/javascript" src="${h.url('/js/mode/meta.js')}"></script>
+<script type="text/javascript" src="${h.url('/js/mode/meta_ext.js')}"></script>
 </%def>
 <%def name="css_extra()">
 <link rel="stylesheet" type="text/css" href="${h.url('/css/codemirror.css')}"/>
@@ -34,24 +38,24 @@
             </li>
         </ul>
     </div>
-    <div class="table">
+    <div class="table" id="edit">
         <div id="files_data">
             <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
             ${h.form(h.url.current(),method='post',id='eform')}
             <div id="body" class="codeblock">
             <div class="code-header">
                 <div class="stats">
-                    <div class="left"><img src="${h.url('/images/icons/file.png')}"/></div>
+                    <div class="left"><i class="icon-file"></i></div>
                     <div class="left item">${h.link_to("r%s:%s" % (c.file.changeset.revision,h.short_id(c.file.changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id))}</div>
                     <div class="left item">${h.format_byte_size(c.file.size,binary=True)}</div>
                     <div class="left item last">${c.file.mimetype}</div>
                     <div class="buttons">
-                      ${h.link_to(_('Show annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
-                      ${h.link_to(_('Show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
-                      ${h.link_to(_('Download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
+                      ${h.link_to(_('Show Annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-mini")}
+                      ${h.link_to(_('Show as Raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-mini")}
+                      ${h.link_to(_('Download as Raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-mini")}
                       % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
                        % if not c.file.is_binary:
-                        ${h.link_to(_('Source'),h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
+                        ${h.link_to(_('Source'),h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-mini")}
                        % endif
                       % endif
                     </div>
@@ -61,38 +65,44 @@
             </div>
                 <pre id="editor_pre"></pre>
                 <textarea id="editor" name="content" style="display:none">${h.escape(c.file.content)|n}</textarea>
-                <div style="padding: 10px;color:#666666">${_('Commit message')}</div>
+                <div style="padding: 10px;color:#666666">${_('Commit Message')}</div>
                 <textarea id="commit" name="message" style="height: 60px;width: 99%;margin-left:4px" placeholder="${c.default_message}"></textarea>
             </div>
             <div style="text-align: left;padding-top: 5px">
-            ${h.submit('commit',_('Commit changes'),class_="ui-btn")}
-            ${h.reset('reset',_('Reset'),class_="ui-btn")}
+            ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")}
+            ${h.reset('reset',_('Reset'),class_="btn btn-small")}
             </div>
             ${h.end_form()}
-            <script type="text/javascript">
-            var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.file.path)}";
-            var myCodeMirror = initCodeMirror('editor',reset_url);
-            CodeMirror.modeURL = "${h.url('/js/mode/%N/%N.js')}";
-
-            //inject new modes
-            var modes_select = YUD.get('set_mode');
-            for(var i=0;i<CodeMirror.modeInfo.length;i++){
-                var m = CodeMirror.modeInfo[i];
-                var opt = new Option(m.name, m.mode);
-                //try to figure out the mode based on the mime-type
-                if(m.mime == "${c.file.mimetype}"){
-                    setCodeMirrorMode(myCodeMirror, m.mode);
-                    opt.selected = "selected";
-                }
-                modes_select.options[i+1] = opt
-            }
-            YUE.on(modes_select, 'change', function(e){
-                var selected = e.currentTarget;
-                var new_mode = selected.options[selected.selectedIndex].value;
-                setCodeMirrorMode(myCodeMirror, new_mode);
-            })
-            </script>
         </div>
     </div>
 </div>
+
+<script type="text/javascript">
+$(document).ready(function(){
+    var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.file.path)}";
+    var myCodeMirror = initCodeMirror('editor',reset_url);
+    CodeMirror.modeURL = "${h.url('/js/mode/%N/%N.js')}";
+
+   //inject new modes, based on codeMirrors modeInfo object
+    var modes_select = $('#set_mode');
+    for(var i=0;i<CodeMirror.modeInfo.length;i++){
+        var m = CodeMirror.modeInfo[i];
+        var opt = $('<option></option>').val(m.mode).text(m.name)
+        modes_select.append(opt)
+    }
+
+    // try to detect the mode based on the file we edit
+    var detected_mode = detectCodeMirrorMode("${c.file.name}", "${c.file.mimetype}")
+    if(detected_mode){
+        setCodeMirrorMode(myCodeMirror, detected_mode);
+        $($('#set_mode option[value="'+detected_mode+'"]')[0]).attr("selected", "selected")
+    }
+
+    $(modes_select).on('change', function(e){
+        var selected = e.currentTarget;
+        var new_mode = selected.options[selected.selectedIndex].value;
+        setCodeMirrorMode(myCodeMirror, new_mode);
+    })
+})
+</script>
 </%def>
--- a/rhodecode/templates/files/files_history_box.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/files/files_history_box.html	Wed Jul 02 19:03:13 2014 -0400
@@ -1,26 +1,8 @@
-<dl>
-    <dt class="file_history">${_('History')}</dt>
-    <dd>
-        <div>
-            <div style="float:left">
-            ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
-            ${h.hidden('diff2',c.file_changeset.raw_id)}
-            ${h.select('diff1',c.file_changeset.raw_id,c.file_history)}
-            ${h.submit('diff',_('Diff to revision'),class_="ui-btn")}
-            ${h.submit('show_rev',_('Show at revision'),class_="ui-btn")}
-            ${h.link_to(_('Show full history'),h.url('changelog_file_home',repo_name=c.repo_name, revision=c.file_changeset.raw_id, f_path=c.f_path),class_="ui-btn")}
-            ${h.hidden('annotate', c.annotate)}
-            ${h.end_form()}
-            </div>
-            <div class="file_author">
-                <div class="item">${h.literal(ungettext(u'%s author',u'%s authors',len(c.authors)) % ('<b>%s</b>' % len(c.authors))) }</div>
-                %for email, user in c.authors:
-                  <div class="contributor tooltip" style="float:left" title="${h.tooltip(user)}">
-                    <div class="gravatar" style="margin:1px"><img alt="gravatar" src="${h.gravatar_url(email, 20)}"/> </div>
-                  </div>
-                %endfor
-            </div>
-        </div>
-        <div style="clear:both"></div>
-    </dd>
-</dl>
+<div class="file_author" style="clear:both">
+    <div class="item">${h.literal(ungettext(u'%s author',u'%s authors',len(c.authors)) % ('<b>%s</b>' % len(c.authors))) }</div>
+    %for email, user in c.authors:
+      <div class="contributor tooltip" style="float:left" title="${h.tooltip(user)}">
+        <div class="gravatar" style="margin:1px"><img alt="gravatar" src="${h.gravatar_url(email, 20)}"/> </div>
+      </div>
+    %endfor
+</div>
--- a/rhodecode/templates/files/files_source.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/files/files_source.html	Wed Jul 02 19:03:13 2014 -0400
@@ -1,34 +1,48 @@
-<div id="node_history">
-%if c.load_full_history:
-    <%include file='files_history_box.html'/>
-%else:
-    <div style="padding-bottom:10px">
-        <span id="load_node_history" class="ui-btn">${_('Load file history')}</span>
+<div id="node_history" style="padding: 0px 0px 10px 0px">
+    <div>
+        <div style="float:left">
+        ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
+        ${h.hidden('diff2',c.file_changeset.raw_id)}
+        ${h.hidden('diff1')}
+        ${h.submit('diff',_('Diff to Revision'),class_="btn btn-small")}
+        ${h.submit('show_rev',_('Show at Revision'),class_="btn btn-small")}
+        ${h.hidden('annotate', c.annotate)}
+        ${h.link_to(_('Show Full History'),h.url('changelog_file_home',repo_name=c.repo_name, revision=c.file_changeset.raw_id, f_path=c.f_path),class_="btn btn-small")}
+        ${h.link_to(_('Show Authors'),'#',class_="btn btn-small" ,id="show_authors")}
+
+        ${h.end_form()}
+        </div>
+        <div id="file_authors" class="file_author" style="clear:both; display: none"></div>
     </div>
-%endif
+    <div style="clear:both"></div>
 </div>
 
 
 <div id="body" class="codeblock">
     <div class="code-header">
         <div class="stats">
-            <div class="left img"><img src="${h.url('/images/icons/file.png')}"/></div>
+            <div class="left img"><i class="icon-file"></i></div>
             <div class="left item"><pre class="tooltip" title="${h.tooltip(h.fmt_date(c.file_changeset.date))}">${h.link_to("r%s:%s" % (c.file_changeset.revision,h.short_id(c.file_changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id))}</pre></div>
             <div class="left item"><pre>${h.format_byte_size(c.file.size,binary=True)}</pre></div>
             <div class="left item last"><pre>${c.file.mimetype}</pre></div>
             <div class="buttons">
               %if c.annotate:
-                ${h.link_to(_('Show source'),    h.url('files_home',         repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
+                ${h.link_to(_('Show Source'),    h.url('files_home',         repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="btn btn-mini")}
               %else:
-                ${h.link_to(_('Show annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
+                ${h.link_to(_('Show Annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="btn btn-mini")}
               %endif
-              ${h.link_to(_('Show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
-              ${h.link_to(_('Download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
+              ${h.link_to(_('Show as Raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="btn btn-mini")}
+              ${h.link_to(_('Download as Raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="btn btn-mini")}
               % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
-               % if c.on_branch_head and c.changeset.branch and not c.file.is_binary:
-                ${h.link_to(_('Edit on branch:%s') % c.changeset.branch,h.url('files_edit_home',repo_name=c.repo_name,revision=c.changeset.branch,f_path=c.f_path),class_="ui-btn")}
+               %if c.on_branch_head and c.changeset.branch and not c.file.is_binary:
+                ${h.link_to(_('Edit on Branch:%s') % c.changeset.branch, h.url('files_edit_home',repo_name=c.repo_name,revision=c.changeset.branch,f_path=c.f_path, anchor='edit'),class_="btn btn-mini")}
+                ${h.link_to(_('Delete'), h.url('files_delete_home',repo_name=c.repo_name,revision=c.changeset.branch,f_path=c.f_path, anchor='edit'),class_="btn btn-mini btn-danger")}
+               %elif c.on_branch_head and c.changeset.branch and c.file.is_binary:
+                ${h.link_to(_('Edit'), '#', class_="btn btn-mini disabled tooltip", title=_('Editing binary files not allowed'))}
+                ${h.link_to(_('Delete'), h.url('files_delete_home',repo_name=c.repo_name,revision=c.changeset.branch,f_path=c.f_path, anchor='edit'),class_="btn btn-mini btn-danger")}
                %else:
-                ${h.link_to(_('Edit on branch:?'), '#', class_="ui-btn disabled tooltip", title=_('Editing files allowed only when on branch head revision'))}
+                ${h.link_to(_('Edit'), '#', class_="btn btn-mini disabled tooltip", title=_('Editing files allowed only when on branch head revision'))}
+                ${h.link_to(_('Delete'), '#', class_="btn btn-mini btn-danger disabled tooltip", title=_('Deleting files allowed only when on branch head revision'))}
                % endif
               % endif
             </div>
@@ -43,7 +57,9 @@
     </div>
     <div class="code-body">
        %if c.file.is_binary:
+           <div style="padding:5px">
            ${_('Binary file (%s)') % c.file.mimetype}
+           </div>
        %else:
         % if c.file.size < c.cut_off_limit:
             %if c.annotate:
@@ -59,51 +75,18 @@
     </div>
 </div>
 
-<script type="text/javascript">
-YUE.onDOMReady(function(){
-    function highlight_lines(lines){
-        for(pos in lines){
-          YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
+<script>
+    $(document).ready(function(){
+        // fake html5 history state
+        var _State = {
+           url: "${h.url.current()}",
+           data: {
+             node_list_url: node_list_url.replace('__REV__',"${c.changeset.raw_id}").replace('__FPATH__', "${h.safe_unicode(c.file.path)}"),
+             url_base: url_base.replace('__REV__',"${c.changeset.raw_id}"),
+             rev:"${c.changeset.raw_id}",
+             f_path: "${h.safe_unicode(c.file.path)}"
+           }
         }
-    }
-    page_highlights = location.href.substring(location.href.indexOf('#')+1).split('L');
-    if (page_highlights.length == 2){
-       highlight_ranges  = page_highlights[1].split(",");
-
-       var h_lines = [];
-       for (pos in highlight_ranges){
-            var _range = highlight_ranges[pos].split('-');
-            if(_range.length == 2){
-                var start = parseInt(_range[0]);
-                var end = parseInt(_range[1]);
-                if (start < end){
-                    for(var i=start;i<=end;i++){
-                        h_lines.push(i);
-                    }
-                }
-            }
-            else{
-                h_lines.push(parseInt(highlight_ranges[pos]));
-            }
-      }
-      highlight_lines(h_lines);
-      var _first_line= YUD.get('L'+h_lines[0]);
-      if(_first_line){
-          _first_line.scrollIntoView()
-      }
-    }
-
-    // select code link event
-    YUE.on('hlcode', 'mouseup', getSelectionLink);
-
-    //load history of file
-    YUE.on('load_node_history', 'click', function(e){
-        var _url = node_history_url.replace('__REV__','${c.file_changeset.raw_id}').replace('__FPATH__', '${c.f_path}');
-        ypjax(_url, 'node_history', function(o){
-            tooltip_activate();
-        })
-    });
-
-   });
-
+        callbacks(_State) // defined in files.html, main callbacks. Triggerd in pjax calls
+    })
 </script>
--- a/rhodecode/templates/files/files_ypjax.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/files/files_ypjax.html	Wed Jul 02 19:03:13 2014 -0400
@@ -4,6 +4,14 @@
         %if c.annotate:
         - ${_('annotation')}
         %endif
+        %if c.file.is_dir():
+          % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
+             / <span title="${_('Add New File')}">
+               <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path, anchor='edit')}">
+                   <i class="icon-plus-sign" style="color:#5bb75b;"></i></a>
+               </span>
+          % endif
+        %endif
     </h3>
         %if c.file.is_dir():
             <%include file='files_browser.html'/>
@@ -12,7 +20,7 @@
         %endif
 %else:
     <h2>
-        <a href="#" onClick="javascript:parent.history.back();" target="main">${_('Go back')}</a>
+        <a href="#" onClick="javascript:parent.history.back();" target="main">${_('Go Back')}</a>
         ${_('No files at given path')}: "${c.f_path or "/"}"
     </h2>
 %endif
--- a/rhodecode/templates/followers/followers.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/followers/followers.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Followers') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s Followers') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
--- a/rhodecode/templates/forks/fork.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/forks/fork.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Fork') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('Fork repository %s') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -12,17 +15,16 @@
 <%def name="page_nav()">
     ${self.menu('repositories')}
 </%def>
+
 <%def name="main()">
-${self.repo_context_bar('showforks')}
+${self.repo_context_bar('createfork')}
 <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>
@@ -33,6 +35,27 @@
                   ${h.hidden('fork_parent_id',c.repo_info.repo_id)}
               </div>
             </div>
+
+            <div class="field">
+                <div class="label label-textarea">
+                    <label for="description">${_('Description')}:</label>
+                </div>
+                <div class="textarea-repo editor">
+                    ${h.textarea('description')}
+                    <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
+                </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")}
+                     <span class="help-block">${_('Optionaly select a group to put this repository into.')}</span>
+                 </div>
+            </div>
+
              <div class="field">
                 <div class="label">
                     <label for="landing_rev">${_('Landing revision')}:</label>
@@ -42,24 +65,7 @@
                     <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
                 </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")}
-                     <span class="help-block">${_('Optionaly select a group to put this repository into.')}</span>
-                 </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)}
-                    <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
-                </div>
-             </div>
+
             <div class="field">
                 <div class="label label-checkbox">
                     <label for="private">${_('Private')}:</label>
@@ -90,10 +96,21 @@
             </div>
             %endif
             <div class="buttons">
-                ${h.submit('',_('Fork this repository'),class_="ui-btn large")}
+                ${h.submit('',_('Fork this Repository'),class_="btn")}
             </div>
         </div>
     </div>
     ${h.end_form()}
 </div>
+<script>
+    $(document).ready(function(){
+        $("#repo_group").select2({
+            'dropdownAutoWidth': true
+        });
+        $("#landing_rev").select2({
+            'minimumResultsForSearch': -1,
+        });
+        $('#repo_name').focus();
+    })
+</script>
 </%def>
--- a/rhodecode/templates/forks/forks.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/forks/forks.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Forks') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s Forks') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
--- a/rhodecode/templates/forks/forks_data.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/forks/forks_data.html	Wed Jul 02 19:03:13 2014 -0400
@@ -17,8 +17,8 @@
             <div class="follower_date">${_('Forked')} -
                 <span class="tooltip" title="${h.tooltip(h.fmt_date(f.created_on))}"> ${h.age(f.created_on)}</span>
                 <a title="${_('Compare fork with %s' % c.repo_name)}"
-                    href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref='default',other_repo=f.repo_name,other_ref_type='branch',other_ref='default')}"
-                    class="ui-btn small">${_('Compare fork')}</a>
+                   href="${h.url('compare_url',repo_name=c.repo_name, org_ref_type=c.rhodecode_db_repo.landing_rev[0],org_ref=c.rhodecode_db_repo.landing_rev[1],other_repo=f.repo_name,other_ref_type=c.rhodecode_db_repo.landing_rev[0],other_ref=c.rhodecode_db_repo.landing_rev[1], merge=1)}"
+                   class="btn btn-small"><i class="icon-loop"></i> ${_('Compare fork')}</a>
             </div>
             <div style="border-bottom: 1px solid #DDD;margin:10px 0px 10px 0px"></div>
         </div>
--- a/rhodecode/templates/index.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/index.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="base/base.html"/>
 
 <%def name="title()">
-${_('Dashboard')} &middot; ${c.rhodecode_name}
+    ${_('Dashboard')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs()">
--- a/rhodecode/templates/index_base.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/index_base.html	Wed Jul 02 19:03:13 2014 -0400
@@ -7,26 +7,31 @@
             </h5>
             %if c.rhodecode_user.username != 'default':
               <ul class="links">
-                %if h.HasPermissionAny('hg.admin','hg.create.repository')() or h.HasReposGroupPermissionAny('group.write', 'group.admin')(c.group.group_name if c.group else None):
-                  <li>
+                <li>
+                <%
+                    gr_name = c.group.group_name if c.group else None
+                    # create repositories with write permission on group is set to true
+                    create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
+                    group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'can write into group index page')
+                    group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
+                %>
+                %if h.HasPermissionAny('hg.admin','hg.create.repository')() or (group_admin or (group_write and create_on_write)):
                   %if c.group:
-                        <span>${h.link_to(_('Add repository'),h.url('new_repo',parent_group=c.group.group_id))}</span>
-                        %if h.HasPermissionAny('hg.admin')() or h.HasReposGroupPermissionAny('group.admin')(c.group.group_name):
-                         <span>${h.link_to(_(u'Add repository group'),h.url('new_repos_group', parent_group=c.group.group_id))}</span>
+                        <a href="${h.url('new_repo',parent_group=c.group.group_id)}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_('Add Repository')}</a>
+                        %if h.HasPermissionAny('hg.admin')() or h.HasRepoGroupPermissionAny('group.admin')(c.group.group_name):
+                            <a href="${h.url('new_repos_group', parent_group=c.group.group_id)}" class="btn btn-small"><i class="icon-plus"></i> ${_(u'Add Repository Group')}</a>
                         %endif
                   %else:
-                    <span>${h.link_to(_('Add repository'),h.url('new_repo'))}</span>
+                    <a href="${h.url('new_repo')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_('Add Repository')}</a>
                     %if h.HasPermissionAny('hg.admin')():
-                     <span>${h.link_to(_(u'Add repository group'),h.url('new_repos_group'))}</span>
+                        <a href="${h.url('new_repos_group')}" class="btn btn-small"><i class="icon-plus"></i> ${_(u'Add Repository Group')}</a>
                     %endif
                   %endif
-                  </li>
                 %endif
-                %if c.group and h.HasReposGroupPermissionAny('group.admin')(c.group.group_name):
-                <li>
-                    <span>${h.link_to(_('Edit repository group'),h.url('edit_repos_group',group_name=c.group.group_name), title=_('You have admin right to this group, and can edit it'))}</span>
+                %if c.group and h.HasRepoGroupPermissionAny('group.admin')(c.group.group_name):
+                    <a href="${h.url('edit_repo_group',group_name=c.group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small"><i class="icon-pencil"></i> ${_('Edit Repository Group')}</a>
+                %endif
                 </li>
-                %endif
               </ul>
             %endif
         </div>
@@ -37,9 +42,9 @@
               <table id="groups_list">
                   <thead>
                       <tr>
-                          <th class="left"><a href="#">${_('Group name')}</a></th>
+                          <th class="left"><a href="#">${_('Group Name')}</a></th>
                           <th class="left"><a href="#">${_('Description')}</a></th>
-                          ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
+                          ##<th class="left"><a href="#">${_('Number of Repositories')}</a></th>
                       </tr>
                   </thead>
 
@@ -48,8 +53,7 @@
                     <tr>
                         <td>
                             <div style="white-space: nowrap">
-                            <img class="icon" alt="${_('Repository group')}" src="${h.url('/images/icons/database_link.png')}"/>
-                            ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
+                                <a href="${url('repos_group_home',group_name=gr.group_name)}"><i class="icon-folder-close"></i> ${gr.name}</a>
                             </div>
                         </td>
                         %if c.visual.stylify_metatags:
@@ -91,6 +95,7 @@
                {key:"desc"},
                {key:"last_change"},
                {key:"last_changeset"},
+               {key:"last_rev_raw"},
                {key:"owner"},
                {key:"atom"},
             ]
--- a/rhodecode/templates/journal/journal.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/journal/journal.html	Wed Jul 02 19:03:13 2014 -0400
@@ -1,14 +1,17 @@
 ## -*- coding: utf-8 -*-
 <%inherit file="/base/base.html"/>
 <%def name="title()">
-    ${_('Journal')} &middot; ${c.rhodecode_name}
+    ${_('Journal')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 <%def name="breadcrumbs()">
     <h5>
     <form id="filter_form">
     <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('quick filter...')}"/>
     <span class="tooltip" title="${h.tooltip(h.journal_filter_help())}">?</span>
-    <input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/>
+    <input type='submit' value="${_('filter')}" class="btn btn-small" style="padding:0px 2px 0px 2px;margin:0px"/>
     ${_('journal')} - ${ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
     </form>
     ${h.end_form()}
@@ -27,12 +30,12 @@
         <!-- box / title -->
         <div class="title">
          ${self.breadcrumbs()}
-         <ul class="links">
+         <ul class="links icon-only-links">
            <li>
-             <span><a id="refresh" href="${h.url('journal')}"><img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/></a></span>
+             <span><a id="refresh" href="${h.url('journal')}"><i class="icon-refresh"></i></a></span>
            </li>
            <li>
-             <span><a href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('ATOM feed')}" alt="${_('ATOM feed')}" src="${h.url('/images/icons/rss_16.png')}"/></a></span>
+             <span><a href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}"><i class="icon-rss-sign"></i></a></span>
            </li>
          </ul>
         </div>
@@ -46,24 +49,24 @@
             <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value="" style="display: none"/>
             <input class="q_filter_box" id="q_filter_watched" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value="" style="display: none"/>
             </h5>
-             <ul class="links" style="color:#DADADA">
-               <li>
-                 <span><a id="show_watched" class="link-white current" href="#watched">${_('Watched')}</a> </span>
+            <ul class="links nav nav-tabs">
+                <li class="active" id="show_watched_li">
+                    <a id="show_watched" href="#watched"><i class="icon-eye-open"></i> ${_('Watched')}</a>
+                </li>
+                <li id="show_my_li">
+                    <a id="show_my" href="#my"><i class="icon-archive"></i> ${_('My repos')}</a>
                </li>
-               <li>
-                 <span><a id="show_my" class="link-white" href="#my">${_('My repos')}</a> </span>
-               </li>
-             </ul>
+            </ul>
         </div>
 
         <!-- end box / title -->
         <div id="my_container" style="display:none">
-            <div class="table yui-skin-sam" id="repos_list_wrap"></div>
+            <div class="table-grid table yui-skin-sam" id="repos_list_wrap"></div>
             <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
         </div>
 
         <div id="watched_container">
-            <div class="table yui-skin-sam" id="watched_repos_list_wrap"></div>
+            <div class="table-grid table yui-skin-sam" id="watched_repos_list_wrap"></div>
             <div id="watched-user-paginator" style="padding: 0px 0px 0px 20px"></div>
         </div>
     </div>
@@ -104,8 +107,8 @@
         YUD.setStyle('q_filter','display','');
         YUD.setStyle('q_filter_watched','display','none');
 
-        YUD.addClass('show_my', 'current');
-        YUD.removeClass('show_watched','current');
+        YUD.addClass('show_my_li', 'active');
+        YUD.removeClass('show_watched_li','active');
 
         if(!YUD.hasClass('show_my', 'loaded')){
             table_renderer(${c.data |n});
@@ -121,8 +124,8 @@
         YUD.setStyle('q_filter_watched','display','');
         YUD.setStyle('q_filter','display','none');
 
-        YUD.addClass('show_watched', 'current');
-        YUD.removeClass('show_my','current');
+        YUD.addClass('show_watched_li', 'active');
+        YUD.removeClass('show_my_li','active');
         if(!YUD.hasClass('show_watched', 'loaded')){
             watched_renderer(${c.watched_data |n});
             YUD.addClass('show_watched', 'loaded');
@@ -166,6 +169,7 @@
                {key:"raw_name"},
                {key:"name"},
                {key:"last_changeset"},
+               {key:"last_rev_raw"},
                {key:"action"},
             ]
          };
@@ -256,6 +260,7 @@
                {key:"raw_name"},
                {key:"name"},
                {key:"last_changeset"},
+               {key:"last_rev_raw"},
                {key:"action"},
             ]
          };
--- a/rhodecode/templates/journal/public_journal.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/journal/public_journal.html	Wed Jul 02 19:03:13 2014 -0400
@@ -1,7 +1,10 @@
 ## -*- coding: utf-8 -*-
 <%inherit file="/base/base.html"/>
 <%def name="title()">
-    ${_('Public Journal')} &middot; ${c.rhodecode_name}
+    ${_('Public Journal')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 <%def name="breadcrumbs()">
     ${c.rhodecode_name}
@@ -21,7 +24,9 @@
     <h5>${_('Public Journal')}</h5>
       <ul class="links">
      <li>
-       <span><a href="${h.url('public_journal_atom')}"><img class="icon" title="${_('ATOM feed')}" alt="${_('ATOM feed')}" src="${h.url('/images/icons/rss_16.png')}"/></a></span>
+       <span>
+         <a href="${h.url('public_journal_atom')}"> <i class="icon-rss-sign" style="color: #fa9b39"></i></a>
+       </span>
      </li>
      </ul>
   </div>
--- a/rhodecode/templates/login.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/login.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,23 +2,35 @@
 <%inherit file="base/root.html"/>
 
 <%def name="title()">
-    ${_('Log In')} &middot; ${c.rhodecode_name}
+    ${_('Log In')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
+
+<div id="header">
+    <div id="header-inner" class="title">
+        <div id="logo">
+            <div class="header">
+                <a href="${h.url('home')}"><img src="/images/rhodecode-logo-white-216x60.png" alt="RhodeCode"/></a>
+            </div>
+            %if c.rhodecode_name:
+             <div class="branding">- ${c.rhodecode_name}</div>
+            %endif
+        </div>
+    </div>
+</div>
+
 <div id="login">
-<div class="flash_msg">
-    <% messages = h.flash.pop_messages() %>
-    % if messages:
-    <ul id="flash-messages">
-        % for message in messages:
-        <li class="${message.category}_msg">${message}</li>
-        % endfor
-    </ul>
-    % endif
-</div>
+    <%include file="/base/flash_msg.html"/>
     <!-- login -->
-    <div class="title top-left-rounded-corner top-right-rounded-corner">
-        <h5>${_('Log In to %s') % c.rhodecode_name}</h5>
+    <div class="title withlogo">
+        %if c.rhodecode_name:
+            <h5>${_('Log In to %s') % c.rhodecode_name}</h5>
+        %else:
+            <h5>${_('Log In')}</h5>
+        %endif
     </div>
     <div class="inner">
         ${h.form(h.url.current(came_from=c.came_from))}
@@ -51,7 +63,7 @@
                     </div>
                 </div>
                 <div class="buttons">
-                    ${h.submit('sign_in',_('Sign In'),class_="ui-btn large")}
+                    ${h.submit('sign_in',_('Sign In'),class_="btn")}
                 </div>
             </div>
             <!-- end fields -->
--- a/rhodecode/templates/password_reset.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/password_reset.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,13 +2,34 @@
 <%inherit file="base/root.html"/>
 
 <%def name="title()">
-    ${_('Password Reset')} &middot; ${c.rhodecode_name}
+    ${_('Password Reset')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
+
+<div id="header">
+    <div id="header-inner" class="title">
+        <div id="logo">
+            <div class="header">
+                <a href="${h.url('home')}"><img src="/images/rhodecode-logo-white-216x60.png" alt="RhodeCode"/></a>
+            </div>
+            %if c.rhodecode_name:
+             <div class="branding">- ${c.rhodecode_name}</div>
+            %endif
+        </div>
+    </div>
+</div>
+
 <div id="register">
-
-    <div class="title top-left-rounded-corner top-right-rounded-corner">
-        <h5>${_('Reset your password to')} ${c.rhodecode_name}</h5>
+    <%include file="/base/flash_msg.html"/>
+    <div class="title withlogo">
+        %if c.rhodecode_name:
+            <h5>${_('Reset your Password to %s') % c.rhodecode_name}</h5>
+        %else:
+            <h5>${_('Reset your Password')}</h5>
+        %endif
     </div>
     <div class="inner">
         ${h.form(url('password_reset'))}
@@ -18,26 +39,48 @@
 
                  <div class="field">
                     <div class="label">
-                        <label for="email">${_('Email address')}:</label>
+                        <label for="email">${_('Email Address')}:</label>
                     </div>
                     <div class="input">
                         ${h.text('email')}
                     </div>
                  </div>
 
+                %if c.captcha_active:
+                <div class="field">
+                    <div class="label">
+                        <label for="email">${_('Captcha')}:</label>
+                    </div>
+                    <div class="input">
+                        ${h.hidden('recaptcha_field')}
+                        <div id="recaptcha"></div>
+                    </div>
+                </div>
+                %endif
+
                 <div class="buttons">
                     <div class="nohighlight">
-                      ${h.submit('send',_('Reset my password'),class_="ui-btn large")}
+                      ${h.submit('send',_('Send password reset email'),class_="btn")}
                           <div class="activation_msg">${_('Password reset link will be send to matching email address')}</div>
                     </div>
                 </div>
             </div>
         </div>
         ${h.end_form()}
+        %if c.captcha_active:
+        <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
+        %endif
         <script type="text/javascript">
-        YUE.onDOMReady(function(){
-            YUD.get('email').focus();
-        })
+         $(document).ready(function(){
+            $('#email').focus();
+            %if c.captcha_active:
+            Recaptcha.create("${c.captcha_public_key}", "recaptcha",
+                {
+                  theme: "white",
+                }
+            );
+            %endif
+         });
         </script>
     </div>
    </div>
--- a/rhodecode/templates/pullrequests/pullrequest.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/pullrequests/pullrequest.html	Wed Jul 02 19:03:13 2014 -0400
@@ -21,9 +21,6 @@
     </div>
 
     ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
-
-    <h3>${_('Create new pull request')}</h3>
-
     <div class="form">
         <!-- fields -->
 
@@ -82,8 +79,8 @@
 
             <div class="field">
                 <div class="buttons">
-                    ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
-                    ${h.reset('reset',_('Reset'),class_="ui-btn large")}
+                    ${h.submit('save',_('Send Pull Request'),class_="btn")}
+                    ${h.reset('reset',_('Reset'),class_="btn")}
                </div>
             </div>
 
@@ -91,10 +88,10 @@
 
         ## Reviewers
         <div style="float:left; border-left:1px dashed #eee">
-            <h4>${_('Pull request reviewers')}</h4>
+            <div class="pr-details-title">${_('Pull request reviewers')}</div>
             <div id="reviewers" style="padding:0px 0px 0px 15px">
               ## members goes here !
-              <div class="group_members_wrap">
+              <div>
                 <ul id="review_members" class="group_members">
                 %for member in [c.default_other_repo_info['user']]:
                   <li id="reviewer_${member['user_id']}">
@@ -102,7 +99,9 @@
                       <div class="gravatar"><img alt="gravatar" src="${member['gravatar_link']}"/> </div>
                       <div style="float:left">${member['firstname']} ${member['lastname']} (${_('owner')})</div>
                       <input type="hidden" value="${member['user_id']}" name="review_members" />
-                      <span class="delete_icon action_button" onclick="removeReviewMember(${member['user_id']})"></span>
+                      <span class="action_button" style="padding: 3px" onclick="removeReviewMember(${member['user_id']})">
+                          <i class="icon-remove-sign"  style="color: #FF4444;"></i>
+                      </span>
                     </div>
                   </li>
                 %endfor
@@ -139,7 +138,7 @@
 
 <script type="text/javascript">
   var _USERS_AC_DATA = ${c.users_array|n};
-  var _GROUPS_AC_DATA = ${c.users_groups_array|n};
+  var _GROUPS_AC_DATA = ${c.user_groups_array|n};
   PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
 
   var other_repos_info = ${c.other_repos_info|n};
--- a/rhodecode/templates/pullrequests/pullrequest_show.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/pullrequests/pullrequest_show.html	Wed Jul 02 19:03:13 2014 -0400
@@ -1,7 +1,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)} &middot; ${c.rhodecode_name}
+    ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -20,14 +23,13 @@
         ${self.breadcrumbs()}
     </div>
 
-    <div class="pr-details-title ${'closed' if c.pull_request.is_closed() else ''}">
-        ${_('Title')}: ${c.pull_request.title}
-        %if c.pull_request.is_closed():
-            (${_('Closed')})
-        %endif
-    </div>
-
-    <div class="form">
+    <div class="form" style="float: left">
+        <div class="pr-details-title ${'closed' if c.pull_request.is_closed() else ''}">
+            ${_('Title')}: ${c.pull_request.title}
+            %if c.pull_request.is_closed():
+                (${_('Closed')})
+            %endif
+        </div>
       <div id="summary" class="fields">
          <div class="field">
           <div class="label-summary">
@@ -36,7 +38,8 @@
           <div class="input">
             <div class="changeset-status-container" style="float:none;clear:both">
             %if c.current_changeset_status:
-              <div class="changeset-status-ico" style="padding:0px 4px 0px 0px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" title="${_('Pull request status calculated from votes')}"/></div>
+              <div class="changeset-status-ico" style="padding:0px 4px 0px 0px">
+                  <img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" title="${_('Pull request status calculated from votes')}"/></div>
               <div class="changeset-status-lbl tooltip" title="${_('Pull request status calculated from votes')}">
                 %if c.pull_request.is_closed():
                     ${_('Closed')},
@@ -66,11 +69,6 @@
           </div>
           <div class="input">
               <div>
-             ##%if h.is_hg(c.pull_request.org_repo):
-             ##  <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
-             ##%elif h.is_git(c.pull_request.org_repo):
-             ##  <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
-             ##%endif
 
               ## branch link is only valid if it is a branch
               <span class="spantag"><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name, anchor=c.pull_request.org_ref_parts[1])}">${c.pull_request.org_ref_parts[0]}: ${c.pull_request.org_ref_parts[1]}</a></span>
@@ -78,7 +76,7 @@
                %if h.is_hg(c.pull_request.org_repo):
                  | ${_('Pull changes')} <span style="font-family: monospace">hg pull -r ${h.short_id(c.cs_ranges[-1].raw_id)} <a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name)}">${c.pull_request.org_repo.clone_url()}</a></span>
                %elif h.is_git(c.pull_request.org_repo):
-                 | ${_('Pull changes')}
+                 | ${_('Pull changes')} <span style="font-family: monospace">git pull <a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name)}">${c.pull_request.org_repo.clone_url()}</a> ${c.pull_request.org_ref_parts[1]} </span>
                %endif
               </div>
           </div>
@@ -88,7 +86,7 @@
               <label>${_('Description')}:</label>
           </div>
           <div class="input">
-              <div style="white-space:pre-wrap">${h.urlify_commit(c.pull_request.description)}</div>
+              <div style="white-space:pre-wrap">${h.urlify_commit(c.pull_request.description, c.repo_name)}</div>
           </div>
          </div>
          <div class="field">
@@ -101,8 +99,50 @@
          </div>
       </div>
     </div>
+    ## REVIEWERS
+    <div style="float:left; border-left:1px dashed #eee">
+       <div class="pr-details-title">${_('Pull request reviewers')}</div>
+        <div id="reviewers" style="padding:0px 0px 5px 10px">
+          ## members goes here !
+          <div style="min-height:30px">
+            <ul id="review_members" class="group_members">
+            %for member,status in c.pull_request_reviewers:
+              <li id="reviewer_${member.user_id}">
+                <div class="reviewers_member">
+                    <div class="reviewer_status tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
+                      <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
+                    </div>
+                  <div class="reviewer_gravatar gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
+                  <div style="float:left;">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
+                  <input type="hidden" value="${member.user_id}" name="review_members" />
+                  %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or c.pull_request.user_id == c.rhodecode_user.user_id):
+                  <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id})">
+                      <i class="icon-remove-sign" style="color: #FF4444;"></i>
+                  </div>
+                  %endif
+                </div>
+              </li>
+            %endfor
+            </ul>
+          </div>
+          %if not c.pull_request.is_closed():
+          <div class='ac'>
+            %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or c.pull_request.author.user_id == c.rhodecode_user.user_id:
+            <div class="reviewer_ac">
+               ${h.text('user', class_='yui-ac-input')}
+               <span class="help-block">${_('Add or remove reviewer to this pull request.')}</span>
+               <div id="reviewers_container"></div>
+            </div>
+            <div style="padding:0px 10px">
+             <span id="update_pull_request" class="btn btn-mini">${_('Save Changes')}</span>
+            </div>
+            %endif
+          </div>
+          %endif
+        </div>
+       </div>
 
-    <div style="overflow: auto;">
+    <div style="overflow: auto; clear: both">
       ##DIFF
       <div class="table" style="float:left;clear:none">
           <div id="body" class="diffblock">
@@ -139,50 +179,10 @@
               % endif
           </div>
       </div>
-      ## REVIEWERS
-       <div style="float:left; border-left:1px dashed #eee">
-       <h4>${_('Pull request reviewers')}</h4>
-        <div id="reviewers" style="padding:0px 0px 5px 10px">
-          ## members goes here !
-          <div class="group_members_wrap" style="min-height:45px">
-            <ul id="review_members" class="group_members">
-            %for member,status in c.pull_request_reviewers:
-              <li id="reviewer_${member.user_id}">
-                <div class="reviewers_member">
-                    <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
-                      <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
-                    </div>
-                  <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
-                  <div style="float:left">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
-                  <input type="hidden" value="${member.user_id}" name="review_members" />
-                  %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.user_id == c.rhodecode_user.user_id):
-                  <span class="delete_icon action_button" onclick="removeReviewMember(${member.user_id})"></span>
-                  %endif
-                </div>
-              </li>
-            %endfor
-            </ul>
-          </div>
-          %if not c.pull_request.is_closed():
-          <div class='ac'>
-            %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
-            <div class="reviewer_ac">
-               ${h.text('user', class_='yui-ac-input')}
-               <span class="help-block">${_('Add or remove reviewer to this pull request.')}</span>
-               <div id="reviewers_container"></div>
-            </div>
-            <div style="padding:0px 10px">
-             <span id="update_pull_request" class="ui-btn xsmall">${_('Save changes')}</span>
-            </div>
-            %endif
-          </div>
-          %endif
-        </div>
-       </div>
     </div>
     <script>
     var _USERS_AC_DATA = ${c.users_array|n};
-    var _GROUPS_AC_DATA = ${c.users_groups_array|n};
+    var _GROUPS_AC_DATA = ${c.user_groups_array|n};
     // TODO: switch this to pyroutes
     AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
     AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
--- a/rhodecode/templates/pullrequests/pullrequest_show_all.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/pullrequests/pullrequest_show_all.html	Wed Jul 02 19:03:13 2014 -0400
@@ -1,7 +1,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Pull Requests') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s Pull Requests') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -23,29 +26,26 @@
     <!-- box / title -->
     <div class="title">
         ${self.breadcrumbs()}
-        %if c.rhodecode_user.username != 'default':
-            <ul class="links">
-            %if h.HasPermissionAny('hg.admin','hg.create.repository')() or h.HasReposGroupPermissionAny('group.write', 'group.admin')(c.group.group_name if c.group else None):
-              <li>
-                  <span>
-                      <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Open new pull request')}</a>
-                  </span>
-              </li>
-            %endif
-            </ul>
-        %endif
+        <ul class="links">
+          <li>
+             %if c.rhodecode_user.username != 'default':
+              <span>
+                  <a id="open_new_pr" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}"><i class="icon-plus"></i> ${_('Open new pull request')}</a>
+              </span>
+             %endif
+              <span>
+                %if c.from_:
+                    <a class="btn btn-small" href="${h.url('pullrequest_show_all',repo_name=c.repo_name,closed=c.closed)}"><i class="icon-loop-2"></i> ${_('Show pull requests to %s') % c.repo_name}</a>
+                %else:
+                    <a class="btn btn-small" href="${h.url('pullrequest_show_all',repo_name=c.repo_name,closed=c.closed,from_=1)}"><i class="icon-loop-2"></i> ${_('Show pull requests from %s') % c.repo_name}</a>
+                %endif
+              </span>
+          </li>
+        </ul>
     </div>
 
     <div style="margin: 0 20px">
         <div>
-        %if c.from_:
-            ${h.link_to(_('Instead, show pull requests to %s') % c.repo_name, h.url('pullrequest_show_all',repo_name=c.repo_name,closed=c.closed))}
-        %else:
-            ${h.link_to(_('Instead, show pull requests from %s') % c.repo_name, h.url('pullrequest_show_all',repo_name=c.repo_name,closed=c.closed,from_=1))}
-        %endif
-        </div>
-
-        <div>
         %if c.closed:
             ${h.link_to(_('Hide closed pull requests'), h.url('pullrequest_show_all',repo_name=c.repo_name,from_=c.from_))}
         %else:
--- a/rhodecode/templates/register.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/register.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,22 +2,33 @@
 <%inherit file="base/root.html"/>
 
 <%def name="title()">
-    ${_('Sign Up')} &middot; ${c.rhodecode_name}
+    ${_('Sign Up')}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
+<div id="header">
+    <div id="header-inner" class="title">
+        <div id="logo">
+            <div class="header">
+                <a href="${h.url('home')}"><img src="/images/rhodecode-logo-white-216x60.png" alt="RhodeCode"/></a>
+            </div>
+            %if c.rhodecode_name:
+             <div class="branding">- ${c.rhodecode_name}</div>
+            %endif
+        </div>
+    </div>
+</div>
+
 
 <div id="register">
-<div class="flash_msg">
-    <% messages = h.flash.pop_messages() %>
-    % if messages:
-    <ul id="flash-messages">
-        % for message in messages:
-        <li class="${message.category}_msg">${message}</li>
-        % endfor
-    </ul>
-    % endif
-</div>
-    <div class="title top-left-rounded-corner top-right-rounded-corner">
-        <h5>${_('Sign Up to')} ${c.rhodecode_name}</h5>
+    <%include file="/base/flash_msg.html"/>
+    <div class="title withlogo">
+        %if c.rhodecode_name:
+            <h5>${_('Sign Up to %s') % c.rhodecode_name}</h5>
+        %else:
+            <h5>${_('Sign Up')}</h5>
+        %endif
     </div>
     <div class="inner">
         ${h.form(url('register'))}
@@ -78,9 +89,21 @@
                     </div>
                 </div>
 
+                %if c.captcha_active:
+                <div class="field">
+                    <div class="label">
+                        <label for="email">${_('Captcha')}:</label>
+                    </div>
+                    <div class="input">
+                        ${h.hidden('recaptcha_field')}
+                        <div id="recaptcha"></div>
+                    </div>
+                </div>
+                %endif
+
                 <div class="buttons">
                     <div class="nohighlight">
-                      ${h.submit('sign_up',_('Sign Up'),class_="ui-btn large")}
+                      ${h.submit('sign_up',_('Sign Up'),class_="btn")}
                       %if c.auto_active:
                           <div class="activation_msg">${_('Your account will be activated right after registration')}</div>
                       %else:
@@ -91,10 +114,21 @@
             </div>
         </div>
         ${h.end_form()}
+        %if c.captcha_active:
+        <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
+        %endif
         <script type="text/javascript">
-        YUE.onDOMReady(function(){
-            YUD.get('username').focus();
-        })
+        $(document).ready(function(){
+            $('#username').focus();
+
+            %if c.captcha_active:
+            Recaptcha.create("${c.captcha_public_key}", "recaptcha",
+                {
+                  theme: "white",
+                }
+            );
+            %endif
+        });
         </script>
     </div>
  </div>
--- a/rhodecode/templates/repo_switcher_list.html	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-## -*- coding: utf-8 -*-
-
-<li class="qfilter_rs">
-    <input type="text" style="border:0;width:100%" placeholder="${_('quick filter...')}" value="" name="filter" id="q_filter_rs" />
-</li>
-
-%for repo in c.repos_list:
-    <li>
-      %if repo['dbrepo']['private'] and c.visual.show_private_icon:
-             <img src="${h.url('/images/icons/private_repo.png')}" alt="${_('Private repository')}" class="repo_switcher_type"/>
-      %elif not repo['dbrepo']['private'] and c.visual.show_public_icon:
-             <img src="${h.url('/images/icons/public_repo.png')}" alt="${_('Public repository')}" class="repo_switcher_type" />
-      %endif
-      ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="repo_name thin %s" % repo['dbrepo']['repo_type'])}
-    </li>
-%endfor
--- a/rhodecode/templates/search/search.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/search/search.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,11 +2,14 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-  %if c.repo_name:
-    ${_('Search repository')} ${c.repo_name} &middot; ${c.rhodecode_name}
-  %else:
-    ${_('Search in all repositories')}
-  %endif
+    %if c.repo_name:
+        ${_('%s Search') % c.repo_name}
+    %else:
+        ${_('Search in all repositories')}
+    %endif
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -51,7 +54,7 @@
              </div>
                 <div class="input">${h.text('q',c.cur_query,class_="small")}
                     <div class="button highlight">
-                        <input type="submit" value="${_('Search')}" class="ui-button"/>
+                        <input type="submit" value="${_('Search')}" class="btn"/>
                     </div>
                 </div>
                 <div style="font-weight: bold;clear:both;margin-left:200px">${c.runtime}</div>
--- a/rhodecode/templates/search/search_commit.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/search/search_commit.html	Wed Jul 02 19:03:13 2014 -0400
@@ -16,7 +16,7 @@
                         <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(sr['author']),20)}"/>
                     </div>
                     <span>${h.person(sr['author'])}</span><br/>
-                    <span><a href="mailto:${h.email_or_none(sr['author'])}">${h.email_or_none(sr['author'])}</a></span><br/>
+                    <span>${h.email_or_none(sr['author'])}</a></span><br/>
                 </div>
                 %if sr['message_hl']:
                 <div class="search-code-body">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/summary/statistics.html	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,450 @@
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('%s Statistics') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${_('Statistics')}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('repositories')}
+</%def>
+
+<%def name="head_extra()">
+<link href="${h.url('atom_feed_home',repo_name=c.rhodecode_db_repo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
+<link href="${h.url('rss_feed_home',repo_name=c.rhodecode_db_repo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
+</%def>
+
+<%def name="main()">
+${self.repo_context_bar('summary')}
+    <%
+    summary = lambda n:{False:'summary-short'}.get(n)
+    %>
+    <div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+
+    <div class="graph">
+         <div style="padding:0 10px 10px 17px;">
+         %if c.no_data:
+           ${c.no_data_msg}
+           %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
+                ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name),class_="btn btn-mini")}
+           %endif
+        %else:
+            ${_('Stats gathered: ')} ${c.stats_percentage}%
+        %endif
+        </div>
+        <div id="commit_history" style="width:450px;height:300px;float:left"></div>
+        <div style="clear: both;height: 10px"></div>
+        <div id="overview" style="width:450px;height:100px;float:left"></div>
+
+        <div id="legend_data" style="clear:both;margin-top:10px;">
+            <div id="legend_container"></div>
+            <div id="legend_choices">
+                <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script type="text/javascript">
+var data = ${c.trending_languages|n};
+var total = 0;
+var no_data = true;
+var tbl = document.createElement('table');
+tbl.setAttribute('class','trending_language_tbl');
+var cnt = 0;
+for (var i=0;i<data.length;i++){
+    total+= data[i][1].count;
+}
+for (var i=0;i<data.length;i++){
+    cnt += 1;
+    no_data = false;
+
+    var hide = cnt>2;
+    var tr = document.createElement('tr');
+    if (hide){
+        tr.setAttribute('style','display:none');
+        tr.setAttribute('class','stats_hidden');
+    }
+    var k = data[i][0];
+    var obj = data[i][1];
+    var percentage = Math.round((obj.count/total*100),2);
+
+    var td1 = document.createElement('td');
+    td1.width = 150;
+    var trending_language_label = document.createElement('div');
+    trending_language_label.innerHTML = obj.desc+" ("+k+")";
+    td1.appendChild(trending_language_label);
+
+    var td2 = document.createElement('td');
+    td2.setAttribute('style','padding-right:14px !important');
+    var trending_language = document.createElement('div');
+    var nr_files = obj.count+" ${_('files')}";
+
+    trending_language.title = k+" "+nr_files;
+
+    if (percentage>22){
+        trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
+    }
+    else{
+        trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
+    }
+
+    trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
+    trending_language.style.width=percentage+"%";
+    td2.appendChild(trending_language);
+
+    tr.appendChild(td1);
+    tr.appendChild(td2);
+    tbl.appendChild(tr);
+    if(cnt == 3){
+        var show_more = document.createElement('tr');
+        var td = document.createElement('td');
+        lnk = document.createElement('a');
+
+        lnk.href='#';
+        lnk.innerHTML = "${_('Show more')}";
+        lnk.id='code_stats_show_more';
+        td.appendChild(lnk);
+
+        show_more.appendChild(td);
+        show_more.appendChild(document.createElement('td'));
+        tbl.appendChild(show_more);
+    }
+
+}
+
+</script>
+<script type="text/javascript">
+/**
+ * Plots summary graph
+ *
+ * @class SummaryPlot
+ * @param {from} initial from for detailed graph
+ * @param {to} initial to for detailed graph
+ * @param {dataset}
+ * @param {overview_dataset}
+ */
+function SummaryPlot(from,to,dataset,overview_dataset) {
+    var initial_ranges = {
+        "xaxis":{
+            "from":from,
+            "to":to,
+        },
+    };
+    var dataset = dataset;
+    var overview_dataset = [overview_dataset];
+    var choiceContainer = YUD.get("legend_choices");
+    var choiceContainerTable = YUD.get("legend_choices_tables");
+    var plotContainer = YUD.get('commit_history');
+    var overviewContainer = YUD.get('overview');
+
+    var plot_options = {
+        bars: {show:true,align:'center',lineWidth:4},
+        legend: {show:true, container:"legend_container"},
+        points: {show:true,radius:0,fill:false},
+        yaxis: {tickDecimals:0,},
+        xaxis: {
+            mode: "time",
+            timeformat: "%d/%m",
+            min:from,
+            max:to,
+        },
+        grid: {
+            hoverable: true,
+            clickable: true,
+            autoHighlight:true,
+            color: "#999"
+        },
+        //selection: {mode: "x"}
+    };
+    var overview_options = {
+        legend:{show:false},
+        bars: {show:true,barWidth: 2,},
+        shadowSize: 0,
+        xaxis: {mode: "time", timeformat: "%d/%m/%y",},
+        yaxis: {ticks: 3, min: 0,tickDecimals:0,},
+        grid: {color: "#999",},
+        selection: {mode: "x"}
+    };
+
+    /**
+    *get dummy data needed in few places
+    */
+    function getDummyData(label){
+        return {"label":label,
+         "data":[{"time":0,
+             "commits":0,
+                 "added":0,
+                 "changed":0,
+                 "removed":0,
+            }],
+            "schema":["commits"],
+            "color":'#ffffff',
+        }
+    }
+
+    /**
+     * generate checkboxes accordindly to data
+     * @param keys
+     * @returns
+     */
+    function generateCheckboxes(data) {
+        //append checkboxes
+        var i = 0;
+        choiceContainerTable.innerHTML = '';
+        for(var pos in data) {
+
+            data[pos].color = i;
+            i++;
+            if(data[pos].label != ''){
+                choiceContainerTable.innerHTML +=
+                    '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
+                     <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
+            }
+        }
+    }
+
+    /**
+     * ToolTip show
+     */
+    function showTooltip(x, y, contents) {
+        var div=document.getElementById('tooltip');
+        if(!div) {
+            div = document.createElement('div');
+            div.id="tooltip";
+            div.style.position="absolute";
+            div.style.border='1px solid #fdd';
+            div.style.padding='2px';
+            div.style.backgroundColor='#fee';
+            document.body.appendChild(div);
+        }
+        YUD.setStyle(div, 'opacity', 0);
+        div.innerHTML = contents;
+        div.style.top=(y + 5) + "px";
+        div.style.left=(x + 5) + "px";
+
+        var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
+        anim.animate();
+    }
+
+    /**
+     * This function will detect if selected period has some changesets
+       for this user if it does this data is then pushed for displaying
+       Additionally it will only display users that are selected by the checkbox
+    */
+    function getDataAccordingToRanges(ranges) {
+
+        var data = [];
+        var new_dataset = {};
+        var keys = [];
+        var max_commits = 0;
+        for(var key in dataset){
+
+            for(var ds in dataset[key].data){
+                commit_data = dataset[key].data[ds];
+                if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
+
+                    if(new_dataset[key] === undefined){
+                        new_dataset[key] = {data:[],schema:["commits"],label:key};
+                    }
+                    new_dataset[key].data.push(commit_data);
+                }
+            }
+            if (new_dataset[key] !== undefined){
+                data.push(new_dataset[key]);
+            }
+        }
+
+        if (data.length > 0){
+            return data;
+        }
+        else{
+            //just return dummy data for graph to plot itself
+            return [getDummyData('')];
+        }
+    }
+
+    /**
+    * redraw using new checkbox data
+    */
+    function plotchoiced(e,args){
+        var cur_data = args[0];
+        var cur_ranges = args[1];
+
+        var new_data = [];
+        var inputs = choiceContainer.getElementsByTagName("input");
+
+        //show only checked labels
+        for(var i=0; i<inputs.length; i++) {
+            var checkbox_key = inputs[i].name;
+
+            if(inputs[i].checked){
+                for(var d in cur_data){
+                    if(cur_data[d].label == checkbox_key){
+                        new_data.push(cur_data[d]);
+                    }
+                }
+            }
+            else{
+                //push dummy data to not hide the label
+                new_data.push(getDummyData(checkbox_key));
+            }
+        }
+
+        var new_options = YAHOO.lang.merge(plot_options, {
+            xaxis: {
+                min: cur_ranges.xaxis.from,
+                max: cur_ranges.xaxis.to,
+                mode:"time",
+                timeformat: "%d/%m",
+            },
+        });
+        if (!new_data){
+            new_data = [[0,1]];
+        }
+        // do the zooming
+       plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
+
+       plot.subscribe("plotselected", plotselected);
+
+       //resubscribe plothover
+       plot.subscribe("plothover", plothover);
+
+       // don't fire event on the overview to prevent eternal loop
+       overview.setSelection(cur_ranges, true);
+
+    }
+
+    /**
+     * plot only selected items from overview
+     * @param ranges
+     * @returns
+     */
+    function plotselected(ranges,cur_data) {
+        //updates the data for new plot
+        var data = getDataAccordingToRanges(ranges);
+        generateCheckboxes(data);
+
+        var new_options = YAHOO.lang.merge(plot_options, {
+            xaxis: {
+                min: ranges.xaxis.from,
+                max: ranges.xaxis.to,
+                mode:"time",
+                timeformat: "%d/%m",
+            },
+        });
+        // do the zooming
+        plot = YAHOO.widget.Flot(plotContainer, data, new_options);
+
+        plot.subscribe("plotselected", plotselected);
+
+        //resubscribe plothover
+        plot.subscribe("plothover", plothover);
+
+        // don't fire event on the overview to prevent eternal loop
+        overview.setSelection(ranges, true);
+
+        //resubscribe choiced
+        YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
+    }
+
+    var previousPoint = null;
+
+    function plothover(o) {
+        var pos = o.pos;
+        var item = o.item;
+
+        //YUD.get("x").innerHTML = pos.x.toFixed(2);
+        //YUD.get("y").innerHTML = pos.y.toFixed(2);
+        if (item) {
+            if (previousPoint != item.datapoint) {
+                previousPoint = item.datapoint;
+
+                var tooltip = YUD.get("tooltip");
+                if(tooltip) {
+                      tooltip.parentNode.removeChild(tooltip);
+                }
+                var x = item.datapoint.x.toFixed(2);
+                var y = item.datapoint.y.toFixed(2);
+
+                if (!item.series.label){
+                    item.series.label = 'commits';
+                }
+                var d = new Date(x*1000);
+                var fd = d.toDateString()
+                var nr_commits = parseInt(y);
+
+                var cur_data = dataset[item.series.label].data[item.dataIndex];
+                var added = cur_data.added;
+                var changed = cur_data.changed;
+                var removed = cur_data.removed;
+
+                var nr_commits_suffix = " ${_('commits')} ";
+                var added_suffix = " ${_('files added')} ";
+                var changed_suffix = " ${_('files changed')} ";
+                var removed_suffix = " ${_('files removed')} ";
+
+                if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
+                if(added==1){added_suffix=" ${_('file added')} ";}
+                if(changed==1){changed_suffix=" ${_('file changed')} ";}
+                if(removed==1){removed_suffix=" ${_('file removed')} ";}
+
+                showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
+                         +'<br/>'+
+                         nr_commits + nr_commits_suffix+'<br/>'+
+                         added + added_suffix +'<br/>'+
+                         changed + changed_suffix + '<br/>'+
+                         removed + removed_suffix + '<br/>');
+            }
+        }
+        else {
+              var tooltip = YUD.get("tooltip");
+
+              if(tooltip) {
+                    tooltip.parentNode.removeChild(tooltip);
+              }
+            previousPoint = null;
+        }
+    }
+
+    /**
+     * MAIN EXECUTION
+     */
+
+    var data = getDataAccordingToRanges(initial_ranges);
+    generateCheckboxes(data);
+
+    //main plot
+    var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
+
+    //overview
+    var overview = YAHOO.widget.Flot(overviewContainer,
+            overview_dataset, overview_options);
+
+    //show initial selection on overview
+    overview.setSelection(initial_ranges);
+
+    plot.subscribe("plotselected", plotselected);
+    plot.subscribe("plothover", plothover)
+
+    overview.subscribe("plotselected", function (ranges) {
+        plot.setSelection(ranges);
+    });
+
+    // user choices on overview
+    YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
+}
+    SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
+</script>
+
+</%def>
--- a/rhodecode/templates/summary/summary.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/summary/summary.html	Wed Jul 02 19:03:13 2014 -0400
@@ -1,11 +1,37 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Summary') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s Summary') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
     ${_('Summary')}
+
+    ## locking icon
+    %if c.rhodecode_db_repo.enable_locking:
+     %if c.rhodecode_db_repo.locked[0]:
+       <span class="locking_locked tooltip" title="${_('Repository locked by %s') % h.person_by_id(c.rhodecode_db_repo.locked[0])}"></span>
+     %else:
+       <span class="locking_unlocked tooltip" title="${_('Repository unlocked')}"></span>
+     %endif
+    %endif
+
+    ##FORK
+    %if c.rhodecode_db_repo.fork:
+    <span>
+        - <i class="icon-code-fork"></i> ${_('Fork of')} "<a href="${h.url('summary_home',repo_name=c.rhodecode_db_repo.fork.repo_name)}">${c.rhodecode_db_repo.fork.repo_name}</a>"
+    </span>
+    %endif
+
+    ##REMOTE
+    %if c.rhodecode_db_repo.clone_uri:
+    <span>
+       - <i class="icon-code-fork"></i> ${_('Clone from')} "<a href="${h.url(str(h.hide_credentials(c.rhodecode_db_repo.clone_uri)))}">${h.hide_credentials(c.rhodecode_db_repo.clone_uri)}</a>"
+    <span>
+    %endif
 </%def>
 
 <%def name="page_nav()">
@@ -13,8 +39,8 @@
 </%def>
 
 <%def name="head_extra()">
-<link href="${h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
-<link href="${h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
+<link href="${h.url('atom_feed_home',repo_name=c.rhodecode_db_repo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
+<link href="${h.url('rss_feed_home',repo_name=c.rhodecode_db_repo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
 
 <script>
 redirect_hash_branch = function(){
@@ -37,11 +63,7 @@
     <%
     summary = lambda n:{False:'summary-short'}.get(n)
     %>
-    %if c.show_stats:
-        <div class="box box-left">
-    %else:
-        <div class="box">
-    %endif
+    <div class="box">
     <!-- box / title -->
     <div class="title">
         ${self.breadcrumbs()}
@@ -49,94 +71,28 @@
     <!-- end box / title -->
     <div class="form">
       <div id="summary" class="fields">
-
-             <div class="field">
-              <div class="label-summary">
-                  <label>${_('Name')}:</label>
-              </div>
-              <div class="input ${summary(c.show_stats)}">
-
-                   ## locking icon
-                    %if c.rhodecode_db_repo.enable_locking:
-                      %if c.rhodecode_db_repo.locked[0]:
-                        <span class="locking_locked tooltip" title="${_('Repository locked by %s') % h.person_by_id(c.rhodecode_db_repo.locked[0])}"></span>
-                      %else:
-                        <span class="locking_unlocked tooltip" title="${_('Repository unlocked')}"></span>
-                      %endif
-                    %endif
-                 ##REPO TYPE
-                 %if h.is_hg(c.dbrepo):
-                   <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
-                 %endif
-                 %if h.is_git(c.dbrepo):
-                   <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
-                 %endif
-
-                 ##PUBLIC/PRIVATE
-                 %if c.dbrepo.private:
-                    <img style="margin-bottom:2px" class="icon" title="${_('Private repository')}" alt="${_('Private repository')}" src="${h.url('/images/icons/private_repo.png')}"/>
-                 %else:
-                    <img style="margin-bottom:2px" class="icon" title="${_('Public repository')}" alt="${_('Public repository')}" src="${h.url('/images/icons/public_repo.png')}"/>
-                 %endif
-
-                  ##REPO NAME
-                  <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
+        <div class="field">
+            <div class="label-summary">
+              <label>${_('Clone url')}:</label>
+            </div>
+            <div class="input ${summary(c.show_stats)}">
+              <input style="width:80%" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
+              <input style="display:none;width:80%" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
+              <div style="display:none" id="clone_by_name" class="btn btn-small clone">${_('Show by Name')}</div>
+              <div id="clone_by_id" class="btn btn-small clone">${_('Show by ID')}</div>
+            </div>
+        </div>
 
-                  ##FORK
-                  %if c.dbrepo.fork:
-                    <div style="margin-top:5px;clear:both">
-                        <img class="icon" alt="${_('Public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
-                        ${_('Fork of')}
-                        <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">${c.dbrepo.fork.repo_name}</a>
-                    </div>
-                  %endif
-                  ##REMOTE
-                  %if c.dbrepo.clone_uri:
-                    <div style="margin-top:5px;clear:both">
-                        ${_('Clone from')}
-                        <img class="icon" alt="${_('Remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
-                        <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}">${h.hide_credentials(c.dbrepo.clone_uri)}</a>
-                    </div>
-                  %endif
-              </div>
-             </div>
-
-             <div class="field">
-              <div class="label-summary">
-                  <label>${_('Description')}:</label>
-              </div>
-              %if c.visual.stylify_metatags:
-                <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(c.dbrepo.description))}</div>
-              %else:
-                <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
-              %endif
-             </div>
-
-             <div class="field">
-              <div class="label-summary">
-                  <label>${_('Contact')}:</label>
-              </div>
-              <div class="input ${summary(c.show_stats)}">
-                  <div class="gravatar">
-                      <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
-                  </div>
-                      ${_('Username')}: ${c.dbrepo.user.username}<br/>
-                      ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
-                      ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
-              </div>
-             </div>
-
-             <div class="field">
-              <div class="label-summary">
-                  <label>${_('Clone url')}:</label>
-              </div>
-              <div class="input ${summary(c.show_stats)}">
-                  <input style="width:${'75%' if c.show_stats else '80%'}" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
-                  <input style="display:none;width:${'75%' if c.show_stats else '80%'}" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
-                  <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
-                  <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
-              </div>
-             </div>
+        <div class="field">
+          <div class="label-summary">
+              <label>${_('Description')}:</label>
+          </div>
+             %if c.visual.stylify_metatags:
+               <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(c.rhodecode_db_repo.description))}</div>
+             %else:
+               <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.rhodecode_db_repo.description)}</div>
+             %endif
+        </div>
 
              <div class="field">
               <div class="label-summary">
@@ -148,7 +104,7 @@
                 %else:
                    ${_('Statistics are disabled for this repository')}
                    %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
-                        ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
+                        ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'),class_="btn btn-mini")}
                    %endif
                 %endif
               </div>
@@ -164,11 +120,13 @@
                 %elif not c.enable_downloads:
                   ${_('Downloads are disabled for this repository')}
                     %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
-                        ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
+                        ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'),class_="btn btn-mini")}
                     %endif
                 %else:
-                    <span id="${'zip_link'}">${h.link_to(_('Download as zip'), h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
-                    ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
+                    <span id="${'zip_link'}">
+                        <a class="btn btn-small" href="${h.url('files_archive_home',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}"><i class="icon-archive"></i> ${_('Download as zip')}</a>
+                    </span>
+                    ${h.hidden('download_options')}
                     <span style="vertical-align: bottom">
                       <input id="archive_subrepos" type="checkbox" name="subrepos" />
                       <label for="archive_subrepos" class="tooltip" title="${h.tooltip(_('Check this to download archive with subrepos'))}" >${_('with subrepos')}</label>
@@ -180,76 +138,55 @@
         <div id="summary-menu-stats">
           <ul>
             <li>
-               <a class="followers" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
-                ${_('Followers')}
-                <span style="float:right" id="current_followers_count">${c.repository_followers}</span>
+               <a title="${_('Owner')} ${c.rhodecode_db_repo.user.email}">
+                <i class="icon-user"></i> ${c.rhodecode_db_repo.user.username}
+                  <div class="gravatar" style="float: right; margin: 0px 0px 0px 0px" title="${c.rhodecode_db_repo.user.name} ${c.rhodecode_db_repo.user.lastname}">
+                     <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email, 18)}"/>
+                  </div>
               </a>
             </li>
             <li>
-              <a class="forks" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
-                ${_('Forks')}
-                <span style="float:right">${c.repository_forks}</span>
+               <a title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
+                <i class="icon-heart"></i> ${_('Followers')}
+                <span class="stats-bullet" id="current_followers_count">${c.repository_followers}</span>
+              </a>
+            </li>
+            <li>
+              <a title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
+                <i class="icon-code-fork"></i> ${_('Forks')}
+                <span class="stats-bullet">${c.repository_forks}</span>
               </a>
             </li>
 
             %if c.rhodecode_user.username != 'default':
             <li class="repo_size">
-              <a href="#" class="repo-size" onclick="javascript:showRepoSize('repo_size_2','${c.dbrepo.repo_name}','${str(h.get_token())}')">${_('Repository Size')}</a>
-              <span id="repo_size_2"></span>
+              <a href="#" onclick="javascript:showRepoSize('repo_size_2','${c.rhodecode_db_repo.repo_name}','${str(h.get_token())}')"><i class="icon-archive"></i> ${_('Repository Size')}</a>
+              <span  class="stats-bullet" id="repo_size_2"></span>
             </li>
             %endif
 
             <li>
             %if c.rhodecode_user.username != 'default':
-              ${h.link_to(_('Feed'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='feed')}
+              <a href="${h.url('atom_feed_home',repo_name=c.rhodecode_db_repo.repo_name,api_key=c.rhodecode_user.api_key)}"><i class="icon-rss-sign"></i> ${_('Feed')}</a>
             %else:
-              ${h.link_to(_('Feed'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='feed')}
+              <a href="${h.url('atom_feed_home',repo_name=c.rhodecode_db_repo.repo_name)}"><i class="icon-rss-sign"></i> ${_('Feed')}</a>
             %endif
             </li>
 
-            %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
-             <li>
-                  ${h.link_to(_('Settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}
-             </li>
+            %if c.show_stats:
+            <li>
+              <a title="${_('Statistics')}" href="${h.url('repo_stats_home',repo_name=c.repo_name)}">
+                <i class="icon-bar-chart"></i> ${_('Statistics')}
+              </a>
+            </li>
             %endif
           </ul>
         </div>
     </div>
 </div>
 
-%if c.show_stats:
-<div class="box box-right"  style="min-height:455px">
-    <!-- box / title -->
-    <div class="title">
-        <h5>${_('Commit activity by day / author')}</h5>
-    </div>
 
-    <div class="graph">
-         <div style="padding:0 10px 10px 17px;">
-         %if c.no_data:
-           ${c.no_data_msg}
-           %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
-                ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
-           %endif
-        %else:
-            ${_('Stats gathered: ')} ${c.stats_percentage}%
-        %endif
-        </div>
-        <div id="commit_history" style="width:450px;height:300px;float:left"></div>
-        <div style="clear: both;height: 10px"></div>
-        <div id="overview" style="width:450px;height:100px;float:left"></div>
-
-        <div id="legend_data" style="clear:both;margin-top:10px;">
-            <div id="legend_container"></div>
-            <div id="legend_choices">
-                <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
-            </div>
-        </div>
-    </div>
-</div>
-%endif
-
-<div class="box">
+<div class="box" style="margin-top: -20px">
     <div class="title">
         <div class="breadcrumbs">
         %if c.repo_changesets:
@@ -269,7 +206,7 @@
 %if c.readme_data:
 <div id="readme" class="anchor">
 <div class="box" style="background-color: #FAFAFA">
-    <div class="title" title="${_('Readme file from revision %s') % c.rhodecode_db_repo.landing_rev}">
+    <div class="title" title="${_('Readme file from revision %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1])}">
         <div class="breadcrumbs">
             <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
         </div>
@@ -316,32 +253,70 @@
     YUD.setStyle('clone_url','display','none');
 })
 
-
-var tmpl_links = {};
-%for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
-  tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
-%endfor
-
-YUE.on(['download_options','archive_subrepos'],'change',function(e){
-   var sm = YUD.get('download_options');
-   var new_cs = sm.options[sm.selectedIndex];
+$(document).ready(function(){
+    var cache = {}
+    $("#download_options").select2({
+        placeholder: _TM['Select changeset'],
+        dropdownAutoWidth: true,
+        query: function(query){
+          var key = 'cache';
+          var cached = cache[key] ;
+          if(cached) {
+            var data = {results: []};
+            //filter results
+            $.each(cached.results, function(){
+                var section = this.text;
+                var children = [];
+                $.each(this.children, function(){
+                    if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
+                        children.push({'id': this.id, 'text': this.text})
+                    }
+                })
+                data.results.push({'text': section, 'children': children})
+            });
+            query.callback(data);
+          }else{
+              $.ajax({
+                url: pyroutes.url('repo_refs_data', {'repo_name': '${c.repo_name}'}),
+                data: {},
+                dataType: 'json',
+                type: 'GET',
+                success: function(data) {
+                  cache[key] = data;
+                  query.callback({results: data.results});
+                }
+              })
+          }
+        },
+    });
+    // on change of download options
+    $('#download_options').change(function(e){
+       var new_cs = e.added
 
-   for(k in tmpl_links){
-       var s = YUD.get(k+'_link');
-       if(s){
-         var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
-         title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
-         title_tmpl = title_tmpl.replace('__CS_EXT__',k);
+       for(k in tmpl_links){
+           var s = $('#'+k+'_link');
+           if(s){
+             var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
+             title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
+             title_tmpl = title_tmpl.replace('__CS_EXT__',k);
+             title_tmpl = '<i class="icon-archive"></i> '+ title_tmpl;
+             var url = tmpl_links[k].replace('__CS__',new_cs.id);
+             var subrepos = $('#archive_subrepos').is(':checked');
+             url = url.replace('__SUB__',subrepos);
+             url = url.replace('__NAME__',title_tmpl);
 
-         var url = tmpl_links[k].replace('__CS__',new_cs.value);
-         var subrepos = YUD.get('archive_subrepos').checked;
-         url = url.replace('__SUB__',subrepos);
-         url = url.replace('__NAME__',title_tmpl);
-         s.innerHTML = url
+             s.html(url)
+           }
        }
-   }
-});
+    });
+
+    var tmpl_links = {};
+    %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
+      tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.rhodecode_db_repo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='btn btn-small')}';
+    %endfor
+})
 </script>
+
 %if c.show_stats:
 <script type="text/javascript">
 var data = ${c.trending_languages|n};
@@ -421,328 +396,6 @@
             'display','none');
 });
 </script>
-<script type="text/javascript">
-/**
- * Plots summary graph
- *
- * @class SummaryPlot
- * @param {from} initial from for detailed graph
- * @param {to} initial to for detailed graph
- * @param {dataset}
- * @param {overview_dataset}
- */
-function SummaryPlot(from,to,dataset,overview_dataset) {
-    var initial_ranges = {
-        "xaxis":{
-            "from":from,
-            "to":to,
-        },
-    };
-    var dataset = dataset;
-    var overview_dataset = [overview_dataset];
-    var choiceContainer = YUD.get("legend_choices");
-    var choiceContainerTable = YUD.get("legend_choices_tables");
-    var plotContainer = YUD.get('commit_history');
-    var overviewContainer = YUD.get('overview');
-
-    var plot_options = {
-        bars: {show:true,align:'center',lineWidth:4},
-        legend: {show:true, container:"legend_container"},
-        points: {show:true,radius:0,fill:false},
-        yaxis: {tickDecimals:0,},
-        xaxis: {
-            mode: "time",
-            timeformat: "%d/%m",
-            min:from,
-            max:to,
-        },
-        grid: {
-            hoverable: true,
-            clickable: true,
-            autoHighlight:true,
-            color: "#999"
-        },
-        //selection: {mode: "x"}
-    };
-    var overview_options = {
-        legend:{show:false},
-        bars: {show:true,barWidth: 2,},
-        shadowSize: 0,
-        xaxis: {mode: "time", timeformat: "%d/%m/%y",},
-        yaxis: {ticks: 3, min: 0,tickDecimals:0,},
-        grid: {color: "#999",},
-        selection: {mode: "x"}
-    };
-
-    /**
-    *get dummy data needed in few places
-    */
-    function getDummyData(label){
-        return {"label":label,
-         "data":[{"time":0,
-             "commits":0,
-                 "added":0,
-                 "changed":0,
-                 "removed":0,
-            }],
-            "schema":["commits"],
-            "color":'#ffffff',
-        }
-    }
-
-    /**
-     * generate checkboxes accordindly to data
-     * @param keys
-     * @returns
-     */
-    function generateCheckboxes(data) {
-        //append checkboxes
-        var i = 0;
-        choiceContainerTable.innerHTML = '';
-        for(var pos in data) {
-
-            data[pos].color = i;
-            i++;
-            if(data[pos].label != ''){
-                choiceContainerTable.innerHTML +=
-                    '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
-                     <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
-            }
-        }
-    }
-
-    /**
-     * ToolTip show
-     */
-    function showTooltip(x, y, contents) {
-        var div=document.getElementById('tooltip');
-        if(!div) {
-            div = document.createElement('div');
-            div.id="tooltip";
-            div.style.position="absolute";
-            div.style.border='1px solid #fdd';
-            div.style.padding='2px';
-            div.style.backgroundColor='#fee';
-            document.body.appendChild(div);
-        }
-        YUD.setStyle(div, 'opacity', 0);
-        div.innerHTML = contents;
-        div.style.top=(y + 5) + "px";
-        div.style.left=(x + 5) + "px";
-
-        var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
-        anim.animate();
-    }
-
-    /**
-     * This function will detect if selected period has some changesets
-       for this user if it does this data is then pushed for displaying
-       Additionally it will only display users that are selected by the checkbox
-    */
-    function getDataAccordingToRanges(ranges) {
-
-        var data = [];
-        var new_dataset = {};
-        var keys = [];
-        var max_commits = 0;
-        for(var key in dataset){
-
-            for(var ds in dataset[key].data){
-                commit_data = dataset[key].data[ds];
-                if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
-
-                    if(new_dataset[key] === undefined){
-                        new_dataset[key] = {data:[],schema:["commits"],label:key};
-                    }
-                    new_dataset[key].data.push(commit_data);
-                }
-            }
-            if (new_dataset[key] !== undefined){
-                data.push(new_dataset[key]);
-            }
-        }
-
-        if (data.length > 0){
-            return data;
-        }
-        else{
-            //just return dummy data for graph to plot itself
-            return [getDummyData('')];
-        }
-    }
-
-    /**
-    * redraw using new checkbox data
-    */
-    function plotchoiced(e,args){
-        var cur_data = args[0];
-        var cur_ranges = args[1];
-
-        var new_data = [];
-        var inputs = choiceContainer.getElementsByTagName("input");
-
-        //show only checked labels
-        for(var i=0; i<inputs.length; i++) {
-            var checkbox_key = inputs[i].name;
-
-            if(inputs[i].checked){
-                for(var d in cur_data){
-                    if(cur_data[d].label == checkbox_key){
-                        new_data.push(cur_data[d]);
-                    }
-                }
-            }
-            else{
-                //push dummy data to not hide the label
-                new_data.push(getDummyData(checkbox_key));
-            }
-        }
-
-        var new_options = YAHOO.lang.merge(plot_options, {
-            xaxis: {
-                min: cur_ranges.xaxis.from,
-                max: cur_ranges.xaxis.to,
-                mode:"time",
-                timeformat: "%d/%m",
-            },
-        });
-        if (!new_data){
-            new_data = [[0,1]];
-        }
-        // do the zooming
-       plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
-
-       plot.subscribe("plotselected", plotselected);
-
-       //resubscribe plothover
-       plot.subscribe("plothover", plothover);
-
-       // don't fire event on the overview to prevent eternal loop
-       overview.setSelection(cur_ranges, true);
-
-    }
-
-    /**
-     * plot only selected items from overview
-     * @param ranges
-     * @returns
-     */
-    function plotselected(ranges,cur_data) {
-        //updates the data for new plot
-        var data = getDataAccordingToRanges(ranges);
-        generateCheckboxes(data);
-
-        var new_options = YAHOO.lang.merge(plot_options, {
-            xaxis: {
-                min: ranges.xaxis.from,
-                max: ranges.xaxis.to,
-                mode:"time",
-                timeformat: "%d/%m",
-            },
-        });
-        // do the zooming
-        plot = YAHOO.widget.Flot(plotContainer, data, new_options);
-
-        plot.subscribe("plotselected", plotselected);
-
-        //resubscribe plothover
-        plot.subscribe("plothover", plothover);
-
-        // don't fire event on the overview to prevent eternal loop
-        overview.setSelection(ranges, true);
-
-        //resubscribe choiced
-        YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
-    }
-
-    var previousPoint = null;
-
-    function plothover(o) {
-        var pos = o.pos;
-        var item = o.item;
-
-        //YUD.get("x").innerHTML = pos.x.toFixed(2);
-        //YUD.get("y").innerHTML = pos.y.toFixed(2);
-        if (item) {
-            if (previousPoint != item.datapoint) {
-                previousPoint = item.datapoint;
-
-                var tooltip = YUD.get("tooltip");
-                if(tooltip) {
-                      tooltip.parentNode.removeChild(tooltip);
-                }
-                var x = item.datapoint.x.toFixed(2);
-                var y = item.datapoint.y.toFixed(2);
-
-                if (!item.series.label){
-                    item.series.label = 'commits';
-                }
-                var d = new Date(x*1000);
-                var fd = d.toDateString()
-                var nr_commits = parseInt(y);
-
-                var cur_data = dataset[item.series.label].data[item.dataIndex];
-                var added = cur_data.added;
-                var changed = cur_data.changed;
-                var removed = cur_data.removed;
-
-                var nr_commits_suffix = " ${_('commits')} ";
-                var added_suffix = " ${_('files added')} ";
-                var changed_suffix = " ${_('files changed')} ";
-                var removed_suffix = " ${_('files removed')} ";
-
-                if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
-                if(added==1){added_suffix=" ${_('file added')} ";}
-                if(changed==1){changed_suffix=" ${_('file changed')} ";}
-                if(removed==1){removed_suffix=" ${_('file removed')} ";}
-
-                showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
-                         +'<br/>'+
-                         nr_commits + nr_commits_suffix+'<br/>'+
-                         added + added_suffix +'<br/>'+
-                         changed + changed_suffix + '<br/>'+
-                         removed + removed_suffix + '<br/>');
-            }
-        }
-        else {
-              var tooltip = YUD.get("tooltip");
-
-              if(tooltip) {
-                    tooltip.parentNode.removeChild(tooltip);
-              }
-            previousPoint = null;
-        }
-    }
-
-    /**
-     * MAIN EXECUTION
-     */
-
-    var data = getDataAccordingToRanges(initial_ranges);
-    generateCheckboxes(data);
-
-    //main plot
-    var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
-
-    //overview
-    var overview = YAHOO.widget.Flot(overviewContainer,
-            overview_dataset, overview_options);
-
-    //show initial selection on overview
-    overview.setSelection(initial_ranges);
-
-    plot.subscribe("plotselected", plotselected);
-    plot.subscribe("plothover", plothover)
-
-    overview.subscribe("plotselected", function (ranges) {
-        plot.setSelection(ranges);
-    });
-
-    // user choices on overview
-    YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
-}
-    SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
-</script>
 %endif
 
 </%def>
--- a/rhodecode/templates/switch_to_list.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/switch_to_list.html	Wed Jul 02 19:03:13 2014 -0400
@@ -1,6 +1,6 @@
 ## -*- coding: utf-8 -*-
 <li>
-    ${h.link_to('%s (%s)' % (_('Branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
+    <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs"><i class="icon-code-fork"></i> ${'%s (%s)' % (_('Branches'),len(c.rhodecode_repo.branches.values()),)}</a>
     <ul>
     %if c.rhodecode_repo.branches.values():
         %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
@@ -13,7 +13,7 @@
 </li>
 %if c.rhodecode_repo.closed_branches.values():
 <li>
-    ${h.link_to('%s (%s)' % (_('Closed Branches'),len(c.rhodecode_repo.closed_branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
+    <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs"><i class="icon-code-fork"></i> ${'%s (%s)' % (_('Closed Branches'),len(c.rhodecode_repo.closed_branches.values()))}</a>
     <ul>
         %for cnt,branch in enumerate(c.rhodecode_repo.closed_branches.items()):
             <li><div><pre>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=(branch[0] if '/' not in branch[0] else branch[1]), at=branch[0]))}</pre></div></li>
@@ -22,7 +22,7 @@
 </li>
 %endif
 <li>
-    ${h.link_to('%s (%s)' % (_('Tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
+    <a href="${h.url('tags_home',repo_name=c.repo_name)}" class="childs"><i class="icon-tag"></i> ${'%s (%s)' % (_('Tags'),len(c.rhodecode_repo.tags.values()),)}</a>
     <ul>
     %if c.rhodecode_repo.tags.values():
         %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
@@ -35,7 +35,7 @@
 </li>
 %if c.rhodecode_repo.alias == 'hg':
 <li>
-    ${h.link_to('%s (%s)' % (_('Bookmarks'),len(c.rhodecode_repo.bookmarks.values()),),h.url('bookmarks_home',repo_name=c.repo_name),class_='bookmarks childs')}
+    <a href="${h.url('bookmarks_home',repo_name=c.repo_name)}" class="childs"><i class="icon-bookmark"></i> ${'%s (%s)' % (_('Bookmarks'),len(c.rhodecode_repo.bookmarks.values()),)}</a>
     <ul>
     %if c.rhodecode_repo.bookmarks.values():
         %for cnt,book in enumerate(c.rhodecode_repo.bookmarks.items()):
--- a/rhodecode/templates/tags/tags.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/tags/tags.html	Wed Jul 02 19:03:13 2014 -0400
@@ -2,7 +2,10 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${_('%s Tags') % c.repo_name} &middot; ${c.rhodecode_name}
+    ${_('%s Tags') % c.repo_name}
+    %if c.rhodecode_name:
+        &middot; ${c.rhodecode_name}
+    %endif
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -23,7 +26,7 @@
     </div>
     <!-- end box / title -->
     %if c.repo_tags:
-    <div class="info_box" id="compare_tags" style="clear: both;padding: 10px 19px;text-align: right;"><a href="#" class="ui-btn small">${_('Compare tags')}</a></div>
+    <div class="info_box" id="compare_tags" style="clear: both;padding: 10px 19px;text-align: right;"><a href="#" class="btn btn-small">${_('Compare tags')}</a></div>
     %endif
     <div class="table">
         <%include file='tags_data.html'/>
@@ -45,7 +48,8 @@
 
 // main table sorting
 var myColumnDefs = [
-    {key:"name",label:"${_('Name')}",sortable:true},
+    {key:"name",label:"${_('Name')}",sortable:true,
+        sortOptions: { sortFunction: nameSort }},
     {key:"date",label:"${_('Date')}",sortable:true,
         sortOptions: { sortFunction: dateSort }},
     {key:"author",label:"${_('Author')}",sortable:true},
@@ -60,9 +64,12 @@
 
 myDataSource.responseSchema = {
     fields: [
+        {key:"raw_name"},
         {key:"name"},
+        {key:"raw_date"},
         {key:"date"},
         {key:"author"},
+        {key:"last_rev_raw"},
         {key:"revision"},
         {key:"compare"},
     ]
--- a/rhodecode/templates/tags/tags_data.html	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/templates/tags/tags_data.html	Wed Jul 02 19:03:13 2014 -0400
@@ -3,15 +3,19 @@
     <table id="tags_data">
       <thead>
         <tr>
+            <th class="left">Raw name</th> ##notranslation
             <th class="left">${_('Name')}</th>
+            <th class="left">Raw date</th> ##notranslation
             <th class="left">${_('Date')}</th>
             <th class="left">${_('Author')}</th>
+            <th class="left">Raw rev</th> ##notranslation
             <th class="left">${_('Revision')}</th>
             <th class="left">${_('Compare')}</th>
         </tr>
       </thead>
         %for cnt,tag in enumerate(c.repo_tags.items()):
         <tr class="parity${cnt%2}">
+            <td>${tag[0]}</td>
             <td>
                 <span class="logtags">
                     <span class="tagtag">${h.link_to(tag[0],
@@ -19,8 +23,10 @@
                     </span>
                 </span>
             </td>
+            <td>${tag[1]._timestamp}</td>
             <td><span class="tooltip" title="${h.tooltip(h.age(tag[1].date))}">${h.fmt_date(tag[1].date)}</span></td>
             <td title="${tag[1].author}">${h.person(tag[1].author)}</td>
+            <td>${tag[1].revision}</td>
             <td>
                 <div>
                     <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id)}">r${tag[1].revision}:${h.short_id(tag[1].raw_id)}</a></pre>
--- a/rhodecode/tests/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,4 +1,19 @@
-"""Pylons application test package
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Pylons application test package
 
 This package assumes the Pylons environment is already loaded, such as
 when this script is imported from the `nosetests --with-pylons=test.ini`
@@ -169,6 +184,7 @@
     def __init__(self, *args, **kwargs):
         BaseTestCase.__init__(self, *args, **kwargs)
         self.app = TestApp(self.wsgiapp)
+        self.maxDiff = None
         self.index_location = config['app_conf']['index_dir']
 
     def log_user(self, username=TEST_USER_ADMIN_LOGIN,
@@ -194,8 +210,7 @@
 
     def checkSessionFlash(self, response, msg):
         self.assertTrue('flash' in response.session,
-                        msg='Response session:%r have no flash'
-                        % response.session)
+                        msg='Response session have no flash key' % response.session)
         if not msg in response.session['flash'][0][1]:
             msg = u'msg `%s` not found in session flash: got `%s` instead' % (
                       msg, response.session['flash'][0][1])
--- a/rhodecode/tests/api/__init__.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/api/__init__.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
--- a/rhodecode/tests/api/api_base.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/api/api_base.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,4 +1,25 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+tests for api. run with::
+
+    RC_WHOOSH_TEST_DISABLE=1 nosetests --with-coverage --cover-package=rhodecode.controllers.api.api -x rhodecode/tests/api
+"""
+
 from __future__ import with_statement
+import os
 import random
 import mock
 
@@ -7,16 +28,19 @@
 from rhodecode.lib.compat import json
 from rhodecode.lib.auth import AuthUser
 from rhodecode.model.user import UserModel
-from rhodecode.model.users_group import UserGroupModel
+from rhodecode.model.user_group import UserGroupModel
 from rhodecode.model.repo import RepoModel
+from rhodecode.model.repo_group import RepoGroupModel
 from rhodecode.model.meta import Session
 from rhodecode.model.scm import ScmModel
-from rhodecode.model.db import Repository, User
-from rhodecode.lib.utils2 import  time_to_datetime
+from rhodecode.model.gist import GistModel
+from rhodecode.model.db import Repository, User, RhodeCodeSetting
+from rhodecode.lib.utils2 import time_to_datetime
 
 
 API_URL = '/_admin/api'
-TEST_USER_GROUP = 'test_users_group'
+TEST_USER_GROUP = 'test_user_group'
+TEST_REPO_GROUP = 'test_repo_group'
 
 fixture = Fixture()
 
@@ -35,6 +59,7 @@
         "args": kw
     })
 
+
 jsonify = lambda obj: json.loads(json.dumps(obj))
 
 
@@ -49,17 +74,18 @@
 
 
 ## helpers
-def make_users_group(name=TEST_USER_GROUP):
+def make_user_group(name=TEST_USER_GROUP):
     gr = fixture.create_user_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
-    UserGroupModel().add_user_to_group(users_group=gr,
+    UserGroupModel().add_user_to_group(user_group=gr,
                                        user=TEST_USER_ADMIN_LOGIN)
     Session().commit()
     return gr
 
 
-def destroy_users_group(name=TEST_USER_GROUP):
-    UserGroupModel().delete(users_group=name, force=True)
+def make_repo_group(name=TEST_REPO_GROUP):
+    gr = fixture.create_repo_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
     Session().commit()
+    return gr
 
 
 class BaseTestApi(object):
@@ -67,7 +93,7 @@
     REPO_TYPE = None
 
     @classmethod
-    def setUpClass(cls):
+    def setup_class(cls):
         cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
         cls.apikey = cls.usr.api_key
         cls.test_user = UserModel().create_or_update(
@@ -82,15 +108,18 @@
         cls.apikey_regular = cls.test_user.api_key
 
     @classmethod
-    def teardownClass(cls):
+    def teardown_class(cls):
         pass
 
     def setUp(self):
         self.maxDiff = None
-        make_users_group()
+        make_user_group()
+        make_repo_group()
 
     def tearDown(self):
-        destroy_users_group()
+        fixture.destroy_user_group(TEST_USER_GROUP)
+        fixture.destroy_gists()
+        fixture.destroy_repo_group(TEST_REPO_GROUP)
 
     def _compare_ok(self, id_, expected, given):
         expected = jsonify({
@@ -110,13 +139,28 @@
         given = json.loads(given)
         self.assertEqual(expected, given)
 
-#    def test_Optional(self):
-#        from rhodecode.controllers.api.api import Optional
-#        option1 = Optional(None)
-#        self.assertEqual('<Optional:%s>' % None, repr(option1))
-#
-#        self.assertEqual(1, Optional.extract(Optional(1)))
-#        self.assertEqual('trololo', Optional.extract('trololo'))
+    def test_Optional_object(self):
+        from rhodecode.controllers.api.api import Optional
+
+        option1 = Optional(None)
+        self.assertEqual('<Optional:%s>' % None, repr(option1))
+        self.assertEqual(option1(), None)
+
+        self.assertEqual(1, Optional.extract(Optional(1)))
+        self.assertEqual('trololo', Optional.extract('trololo'))
+
+    def test_Optional_OAttr(self):
+        from rhodecode.controllers.api.api import Optional, OAttr
+
+        option1 = Optional(OAttr('apiuser'))
+        self.assertEqual('apiuser', Optional.extract(option1))
+
+    def test_OAttr_object(self):
+        from rhodecode.controllers.api.api import OAttr
+
+        oattr1 = OAttr('apiuser')
+        self.assertEqual('<OptionalAttr:apiuser>', repr(oattr1))
+        self.assertEqual(oattr1(), oattr1)
 
     def test_api_wrong_key(self):
         id_, params = _build_data('trololo', 'get_user')
@@ -149,23 +193,35 @@
         self._compare_error(id_, expected, given=response.body)
 
     def test_api_args_is_null(self):
-        id_, params = _build_data(self.apikey, 'get_users',)
+        id_, params = _build_data(self.apikey, 'get_users', )
         params = params.replace('"args": {}', '"args": null')
         response = api_call(self, params)
         self.assertEqual(response.status, '200 OK')
 
     def test_api_args_is_bad(self):
-        id_, params = _build_data(self.apikey, 'get_users',)
+        id_, params = _build_data(self.apikey, 'get_users', )
         params = params.replace('"args": {}', '"args": 1')
         response = api_call(self, params)
         self.assertEqual(response.status, '200 OK')
 
+    def test_api_args_different_args(self):
+        import string
+        expected = {
+            'ascii_letters': string.ascii_letters,
+            'ws': string.whitespace,
+            'printables': string.printable
+        }
+        id_, params = _build_data(self.apikey, 'test', args=expected)
+        response = api_call(self, params)
+        self.assertEqual(response.status, '200 OK')
+        self._compare_ok(id_, expected, response.body)
+
     def test_api_get_users(self):
-        id_, params = _build_data(self.apikey, 'get_users',)
+        id_, params = _build_data(self.apikey, 'get_users', )
         response = api_call(self, params)
         ret_all = []
-        _users = User.query().filter(User.username != User.DEFAULT_USER)\
-                             .order_by(User.username).all()
+        _users = User.query().filter(User.username != User.DEFAULT_USER) \
+            .order_by(User.username).all()
         for usr in _users:
             ret = usr.get_api_data()
             ret_all.append(jsonify(ret))
@@ -223,28 +279,25 @@
         self._compare_error(id_, expected, given=response.body)
 
     def test_api_pull(self):
-        #TODO: issues with rhodecode_extras here.. not sure why !
-        pass
+        repo_name = 'test_pull'
+        r = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
+        r.clone_uri = os.path.join(TESTS_TMP_PATH, self.REPO)
+        Session.add(r)
+        Session.commit()
 
-#        repo_name = 'test_pull'
-#        r = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
-#        r.clone_uri = TEST_self.REPO
-#        Session.add(r)
-#        Session.commit()
-#
-#        id_, params = _build_data(self.apikey, 'pull',
-#                                  repoid=repo_name,)
-#        response = self.app.post(API_URL, content_type='application/json',
-#                                 params=params)
-#
-#        expected = 'Pulled from `%s`' % repo_name
-#        self._compare_ok(id_, expected, given=response.body)
-#
-#        fixture.destroy_repo(repo_name)
+        id_, params = _build_data(self.apikey, 'pull',
+                                  repoid=repo_name,)
+        response = api_call(self, params)
+
+        expected = {'msg': 'Pulled from `%s`' % repo_name,
+                    'repository': repo_name}
+        self._compare_ok(id_, expected, given=response.body)
+
+        fixture.destroy_repo(repo_name)
 
     def test_api_pull_error(self):
         id_, params = _build_data(self.apikey, 'pull',
-                                  repoid=self.REPO,)
+                                  repoid=self.REPO, )
         response = api_call(self, params)
 
         expected = 'Unable to pull changes from `%s`' % self.REPO
@@ -259,7 +312,7 @@
 
     @mock.patch.object(ScmModel, 'repo_scan', crash)
     def test_api_rescann_error(self):
-        id_, params = _build_data(self.apikey, 'rescan_repos',)
+        id_, params = _build_data(self.apikey, 'rescan_repos', )
         response = api_call(self, params)
 
         expected = 'Error occurred during rescan repositories action'
@@ -267,13 +320,16 @@
 
     def test_api_invalidate_cache(self):
         repo = RepoModel().get_by_repo_name(self.REPO)
-        repo.scm_instance_cached() # seed cache
+        repo.scm_instance_cached()  # seed cache
 
         id_, params = _build_data(self.apikey, 'invalidate_cache',
                                   repoid=self.REPO)
         response = api_call(self, params)
 
-        expected = ("Caches of repository `%s` was invalidated" % (self.REPO))
+        expected = {
+            'msg': "Cache for repository `%s` was invalidated" % (self.REPO,),
+            'repository': self.REPO
+        }
         self._compare_ok(id_, expected, given=response.body)
 
     @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
@@ -285,6 +341,17 @@
         expected = 'Error occurred during cache invalidation action'
         self._compare_error(id_, expected, given=response.body)
 
+    def test_api_invalidate_cache_regular_user_no_permission(self):
+        repo = RepoModel().get_by_repo_name(self.REPO)
+        repo.scm_instance_cached() # seed cache
+
+        id_, params = _build_data(self.apikey_regular, 'invalidate_cache',
+                                  repoid=self.REPO)
+        response = api_call(self, params)
+
+        expected = "repository `%s` does not exist" % (self.REPO,)
+        self._compare_error(id_, expected, given=response.body)
+
     def test_api_lock_repo_lock_aquire(self):
         id_, params = _build_data(self.apikey, 'lock',
                                   userid=TEST_USER_ADMIN_LOGIN,
@@ -292,14 +359,13 @@
                                   locked=True)
         response = api_call(self, params)
         expected = {
-            'repo': self.REPO,
-            'locked': True,
-            'locked_since': None,
+            'repo': self.REPO, 'locked': True,
+            'locked_since': response.json['result']['locked_since'],
             'locked_by': TEST_USER_ADMIN_LOGIN,
+            'lock_state_changed': True,
             'msg': ('User `%s` set lock state for repo `%s` to `%s`'
                     % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
         }
-        expected['locked_since'] = json.loads(response.body)['result']['locked_since']
         self._compare_ok(id_, expected, given=response.body)
 
     def test_api_lock_repo_lock_aquire_by_non_admin(self):
@@ -314,12 +380,12 @@
             expected = {
                 'repo': repo_name,
                 'locked': True,
-                'locked_since': None,
+                'locked_since': response.json['result']['locked_since'],
                 'locked_by': self.TEST_USER_LOGIN,
+                'lock_state_changed': True,
                 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
                         % (self.TEST_USER_LOGIN, repo_name, True))
             }
-            expected['locked_since'] = json.loads(response.body)['result']['locked_since']
             self._compare_ok(id_, expected, given=response.body)
         finally:
             fixture.destroy_repo(repo_name)
@@ -358,6 +424,7 @@
             'locked': False,
             'locked_since': None,
             'locked_by': TEST_USER_ADMIN_LOGIN,
+            'lock_state_changed': True,
             'msg': ('User `%s` set lock state for repo `%s` to `%s`'
                     % (TEST_USER_ADMIN_LOGIN, self.REPO, False))
         }
@@ -368,34 +435,56 @@
                                   repoid=self.REPO,
                                   locked=True)
         response = api_call(self, params)
+        time_ = response.json['result']['locked_since']
         expected = {
             'repo': self.REPO,
             'locked': True,
-            'locked_since': None,
+            'locked_since': time_,
             'locked_by': TEST_USER_ADMIN_LOGIN,
+            'lock_state_changed': True,
             'msg': ('User `%s` set lock state for repo `%s` to `%s`'
                     % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
         }
-        expected['locked_since'] = json.loads(response.body)['result']['locked_since']
+
         self._compare_ok(id_, expected, given=response.body)
 
     def test_api_lock_repo_lock_optional_locked(self):
         id_, params = _build_data(self.apikey, 'lock',
                                   repoid=self.REPO)
         response = api_call(self, params)
-        time_ = json.loads(response.body)['result']['locked_since']
+        time_ = response.json['result']['locked_since']
         expected = {
             'repo': self.REPO,
             'locked': True,
-            'locked_since': None,
+            'locked_since': time_,
             'locked_by': TEST_USER_ADMIN_LOGIN,
-            'msg': ('Repo `%s` locked by `%s`. '
-                            % (self.REPO,
-                               json.dumps(time_to_datetime(time_))))
+            'lock_state_changed': False,
+            'msg': ('Repo `%s` locked by `%s` on `%s`.'
+                    % (self.REPO, TEST_USER_ADMIN_LOGIN,
+                       json.dumps(time_to_datetime(time_))))
+        }
+        self._compare_ok(id_, expected, given=response.body)
 
-        }
-        expected['locked_since'] = time_
-        self._compare_ok(id_, expected, given=response.body)
+    def test_api_lock_repo_lock_optional_not_locked(self):
+        repo_name = 'api_not_locked'
+        repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
+                            cur_user=self.TEST_USER_LOGIN)
+        self.assertEqual(repo.locked, [None, None])
+        try:
+            id_, params = _build_data(self.apikey, 'lock',
+                                      repoid=repo.repo_id)
+            response = api_call(self, params)
+            expected = {
+                'repo': repo_name,
+                'locked': False,
+                'locked_since': None,
+                'locked_by': None,
+                'lock_state_changed': False,
+                'msg': ('Repo `%s` not locked.' % (repo_name,))
+            }
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_repo(repo_name)
 
     @mock.patch.object(Repository, 'lock', crash)
     def test_api_lock_error(self):
@@ -427,6 +516,33 @@
         expected = []
         self._compare_ok(id_, expected, given=response.body)
 
+    def test_api_get_locks_with_one_locked_repo(self):
+        repo_name = 'api_delete_me'
+        repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
+                                   cur_user=self.TEST_USER_LOGIN)
+        Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
+        try:
+            id_, params = _build_data(self.apikey, 'get_locks')
+            response = api_call(self, params)
+            expected = [repo.get_api_data()]
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_repo(repo_name)
+
+    def test_api_get_locks_with_one_locked_repo_for_specific_user(self):
+        repo_name = 'api_delete_me'
+        repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
+                                   cur_user=self.TEST_USER_LOGIN)
+        Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
+        try:
+            id_, params = _build_data(self.apikey, 'get_locks',
+                                      userid=self.TEST_USER_LOGIN)
+            response = api_call(self, params)
+            expected = [repo.get_api_data()]
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_repo(repo_name)
+
     def test_api_get_locks_with_userid(self):
         id_, params = _build_data(self.apikey, 'get_locks',
                                   userid=TEST_USER_REGULAR_LOGIN)
@@ -470,11 +586,11 @@
             user=jsonify(usr.get_api_data())
         )
 
-        expected = ret
-        self._compare_ok(id_, expected, given=response.body)
-
-        UserModel().delete(usr.user_id)
-        Session().commit()
+        try:
+            expected = ret
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_user(usr.user_id)
 
     def test_api_create_user_without_password(self):
         username = 'test_new_api_user_passwordless'
@@ -490,12 +606,31 @@
             msg='created new user `%s`' % username,
             user=jsonify(usr.get_api_data())
         )
+        try:
+            expected = ret
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_user(usr.user_id)
 
-        expected = ret
-        self._compare_ok(id_, expected, given=response.body)
+    def test_api_create_user_with_extern_name(self):
+        username = 'test_new_api_user_passwordless'
+        email = username + "@foo.com"
+
+        id_, params = _build_data(self.apikey, 'create_user',
+                                  username=username,
+                                  email=email, extern_name='rhodecode')
+        response = api_call(self, params)
 
-        UserModel().delete(usr.user_id)
-        Session().commit()
+        usr = UserModel().get_by_username(username)
+        ret = dict(
+            msg='created new user `%s`' % username,
+            user=jsonify(usr.get_api_data())
+        )
+        try:
+            expected = ret
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_user(usr.user_id)
 
     @mock.patch.object(UserModel, 'create_or_update', crash)
     def test_api_create_user_when_exception_happened(self):
@@ -523,7 +658,7 @@
         ## DELETE THIS USER NOW
 
         id_, params = _build_data(self.apikey, 'delete_user',
-                                  userid=username,)
+                                  userid=username, )
         response = api_call(self, params)
 
         ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
@@ -541,10 +676,10 @@
         username = usr.username
 
         id_, params = _build_data(self.apikey, 'delete_user',
-                                  userid=username,)
+                                  userid=username, )
         response = api_call(self, params)
-        ret = 'failed to delete ID:%s %s' % (usr.user_id,
-                                             usr.username)
+        ret = 'failed to delete user ID:%s %s' % (usr.user_id,
+                                                  usr.username)
         expected = ret
         self._compare_error(id_, expected, given=response.body)
 
@@ -553,12 +688,14 @@
                            ('email', 'new_username'),
                            ('admin', True),
                            ('admin', False),
-                           ('ldap_dn', 'test'),
-                           ('ldap_dn', None),
+                           ('extern_type', 'ldap'),
+                           ('extern_type', None),
+                           ('extern_name', 'test'),
+                           ('extern_name', None),
                            ('active', False),
                            ('active', True),
                            ('password', 'newpass')
-                           ])
+    ])
     def test_api_update_user(self, name, expected):
         usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
         kw = {name: expected,
@@ -567,10 +704,11 @@
         response = api_call(self, params)
 
         ret = {
-        'msg': 'updated user ID:%s %s' % (usr.user_id, self.TEST_USER_LOGIN),
-        'user': jsonify(UserModel()\
-                            .get_by_username(self.TEST_USER_LOGIN)\
-                            .get_api_data())
+            'msg': 'updated user ID:%s %s' % (
+                usr.user_id, self.TEST_USER_LOGIN),
+            'user': jsonify(UserModel() \
+                .get_by_username(self.TEST_USER_LOGIN) \
+                .get_api_data())
         }
 
         expected = ret
@@ -584,8 +722,9 @@
 
         response = api_call(self, params)
         ret = {
-        'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN),
-        'user': ret
+            'msg': 'updated user ID:%s %s' % (
+                usr.user_id, TEST_USER_ADMIN_LOGIN),
+            'user': ret
         }
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
@@ -598,12 +737,22 @@
 
         response = api_call(self, params)
         ret = {
-        'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN),
-        'user': ret
+            'msg': 'updated user ID:%s %s' % (
+                usr.user_id, TEST_USER_ADMIN_LOGIN),
+            'user': ret
         }
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
+    def test_api_update_user_default_user(self):
+        usr = User.get_default_user()
+        id_, params = _build_data(self.apikey, 'update_user',
+                                  userid=usr.user_id)
+
+        response = api_call(self, params)
+        expected = 'editing default user is forbidden'
+        self._compare_error(id_, expected, given=response.body)
+
     @mock.patch.object(UserModel, 'update_user', crash)
     def test_api_update_user_when_exception_happens(self):
         usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
@@ -619,10 +768,10 @@
 
     def test_api_get_repo(self):
         new_group = 'some_new_group'
-        make_users_group(new_group)
-        RepoModel().grant_users_group_permission(repo=self.REPO,
-                                                 group_name=new_group,
-                                                 perm='repository.read')
+        make_user_group(new_group)
+        RepoModel().grant_user_group_permission(repo=self.REPO,
+                                                group_name=new_group,
+                                                perm='repository.read')
         Session().commit()
         id_, params = _build_data(self.apikey, 'get_repo',
                                   repoid=self.REPO)
@@ -636,18 +785,16 @@
         for user in repo.repo_to_perm:
             perm = user.permission.permission_name
             user = user.user
-            user_data = user.get_api_data()
-            user_data['type'] = "user"
-            user_data['permission'] = perm
+            user_data = {'name': user.username, 'type': "user",
+                         'permission': perm}
             members.append(user_data)
 
-        for users_group in repo.users_group_to_perm:
-            perm = users_group.permission.permission_name
-            users_group = users_group.users_group
-            users_group_data = users_group.get_api_data()
-            users_group_data['type'] = "users_group"
-            users_group_data['permission'] = perm
-            members.append(users_group_data)
+        for user_group in repo.users_group_to_perm:
+            perm = user_group.permission.permission_name
+            user_group = user_group.users_group
+            user_group_data = {'name': user_group.users_group_name,
+                               'type': "user_group", 'permission': perm}
+            members.append(user_group_data)
 
         for user in repo.followers:
             followers.append(user.user.get_api_data())
@@ -657,10 +804,19 @@
 
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
-        destroy_users_group(new_group)
+        fixture.destroy_user_group(new_group)
 
-    def test_api_get_repo_by_non_admin(self):
-        id_, params = _build_data(self.apikey, 'get_repo',
+    @parameterized.expand([
+        ('repository.admin',),
+        ('repository.write',),
+        ('repository.read',),
+    ])
+    def test_api_get_repo_by_non_admin(self, grant_perm):
+        RepoModel().grant_user_permission(repo=self.REPO,
+                                          user=self.TEST_USER_LOGIN,
+                                          perm=grant_perm)
+        Session().commit()
+        id_, params = _build_data(self.apikey_regular, 'get_repo',
                                   repoid=self.REPO)
         response = api_call(self, params)
 
@@ -669,21 +825,20 @@
 
         members = []
         followers = []
+        self.assertEqual(2, len(repo.repo_to_perm))
         for user in repo.repo_to_perm:
             perm = user.permission.permission_name
-            user = user.user
-            user_data = user.get_api_data()
-            user_data['type'] = "user"
-            user_data['permission'] = perm
+            user_obj = user.user
+            user_data = {'name': user_obj.username, 'type': "user",
+                         'permission': perm}
             members.append(user_data)
 
-        for users_group in repo.users_group_to_perm:
-            perm = users_group.permission.permission_name
-            users_group = users_group.users_group
-            users_group_data = users_group.get_api_data()
-            users_group_data['type'] = "users_group"
-            users_group_data['permission'] = perm
-            members.append(users_group_data)
+        for user_group in repo.users_group_to_perm:
+            perm = user_group.permission.permission_name
+            user_group_obj = user_group.users_group
+            user_group_data = {'name': user_group_obj.users_group_name,
+                               'type': "user_group", 'permission': perm}
+            members.append(user_group_data)
 
         for user in repo.followers:
             followers.append(user.user.get_api_data())
@@ -692,7 +847,10 @@
         ret['followers'] = followers
 
         expected = ret
-        self._compare_ok(id_, expected, given=response.body)
+        try:
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
 
     def test_api_get_repo_by_non_admin_no_permission_to_repo(self):
         RepoModel().grant_user_permission(repo=self.REPO,
@@ -753,7 +911,7 @@
 
         # we don't the actual return types here since it's tested somewhere
         # else
-        expected = json.loads(response.body)['result']
+        expected = response.json['result']
         self._compare_ok(id_, expected, given=response.body)
 
     def test_api_get_repo_nodes_bad_revisions(self):
@@ -761,7 +919,7 @@
         path = '/'
         id_, params = _build_data(self.apikey, 'get_repo_nodes',
                                   repoid=self.REPO, revision=rev,
-                                  root_path=path,)
+                                  root_path=path, )
         response = api_call(self, params)
 
         expected = 'failed to get repo: `%s` nodes' % self.REPO
@@ -772,7 +930,7 @@
         path = '/idontexits'
         id_, params = _build_data(self.apikey, 'get_repo_nodes',
                                   repoid=self.REPO, revision=rev,
-                                  root_path=path,)
+                                  root_path=path, )
         response = api_call(self, params)
 
         expected = 'failed to get repo: `%s` nodes' % self.REPO
@@ -788,35 +946,83 @@
                                   ret_type=ret_type)
         response = api_call(self, params)
 
-        expected = 'ret_type must be one of %s' % (['files', 'dirs', 'all'])
+        expected = ('ret_type must be one of %s'
+                    % (','.join(['files', 'dirs', 'all'])))
         self._compare_error(id_, expected, given=response.body)
 
+    @parameterized.expand([('all', 'all', 'repository.write'),
+                           ('dirs', 'dirs', 'repository.admin'),
+                           ('files', 'files', 'repository.read'), ])
+    def test_api_get_repo_nodes_by_regular_user(self, name, ret_type, grant_perm):
+        RepoModel().grant_user_permission(repo=self.REPO,
+                                          user=self.TEST_USER_LOGIN,
+                                          perm=grant_perm)
+        Session().commit()
+
+        rev = 'tip'
+        path = '/'
+        id_, params = _build_data(self.apikey_regular, 'get_repo_nodes',
+                                  repoid=self.REPO, revision=rev,
+                                  root_path=path,
+                                  ret_type=ret_type)
+        response = api_call(self, params)
+
+        # we don't the actual return types here since it's tested somewhere
+        # else
+        expected = response.json['result']
+        try:
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
+
     def test_api_create_repo(self):
         repo_name = 'api-repo'
         id_, params = _build_data(self.apikey, 'create_repo',
-                                    repo_name=repo_name,
-                                    owner=TEST_USER_ADMIN_LOGIN,
-                                    repo_type='hg',
-                                  )
+                                  repo_name=repo_name,
+                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  repo_type=self.REPO_TYPE,
+        )
         response = api_call(self, params)
 
         repo = RepoModel().get_by_repo_name(repo_name)
+        self.assertNotEqual(repo, None)
         ret = {
             'msg': 'Created new repository `%s`' % repo_name,
-            'repo': jsonify(repo.get_api_data())
+            'success': True,
+            'task': None,
         }
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
         fixture.destroy_repo(repo_name)
 
+    def test_api_create_repo_in_group(self):
+        repo_name = 'my_gr/api-repo'
+        id_, params = _build_data(self.apikey, 'create_repo',
+                                  repo_name=repo_name,
+                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  repo_type=self.REPO_TYPE,)
+        response = api_call(self, params)
+        print params
+        repo = RepoModel().get_by_repo_name(repo_name)
+        self.assertNotEqual(repo, None)
+        ret = {
+            'msg': 'Created new repository `%s`' % repo_name,
+            'success': True,
+            'task': None,
+        }
+        expected = ret
+        self._compare_ok(id_, expected, given=response.body)
+        fixture.destroy_repo(repo_name)
+        fixture.destroy_repo_group('my_gr')
+
     def test_api_create_repo_unknown_owner(self):
         repo_name = 'api-repo'
         owner = 'i-dont-exist'
         id_, params = _build_data(self.apikey, 'create_repo',
-                                    repo_name=repo_name,
-                                    owner=owner,
-                                    repo_type='hg',
-                                  )
+                                  repo_name=repo_name,
+                                  owner=owner,
+                                  repo_type=self.REPO_TYPE,
+        )
         response = api_call(self, params)
         expected = 'user `%s` does not exist' % owner
         self._compare_error(id_, expected, given=response.body)
@@ -825,15 +1031,17 @@
         repo_name = 'api-repo'
         owner = 'i-dont-exist'
         id_, params = _build_data(self.apikey, 'create_repo',
-                                    repo_name=repo_name,
-                                    repo_type='hg',
-                                  )
+                                  repo_name=repo_name,
+                                  repo_type=self.REPO_TYPE,
+        )
         response = api_call(self, params)
 
         repo = RepoModel().get_by_repo_name(repo_name)
+        self.assertNotEqual(repo, None)
         ret = {
             'msg': 'Created new repository `%s`' % repo_name,
-            'repo': jsonify(repo.get_api_data())
+            'success': True,
+            'task': None,
         }
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
@@ -843,15 +1051,17 @@
         repo_name = 'api-repo'
         owner = 'i-dont-exist'
         id_, params = _build_data(self.apikey_regular, 'create_repo',
-                                    repo_name=repo_name,
-                                    repo_type='hg',
-                                  )
+                                  repo_name=repo_name,
+                                  repo_type=self.REPO_TYPE,
+        )
         response = api_call(self, params)
 
         repo = RepoModel().get_by_repo_name(repo_name)
+        self.assertNotEqual(repo, None)
         ret = {
             'msg': 'Created new repository `%s`' % repo_name,
-            'repo': jsonify(repo.get_api_data())
+            'success': True,
+            'task': None,
         }
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
@@ -861,10 +1071,9 @@
         repo_name = 'api-repo'
         owner = 'i-dont-exist'
         id_, params = _build_data(self.apikey_regular, 'create_repo',
-                                    repo_name=repo_name,
-                                    repo_type='hg',
-                                    owner=owner
-                                  )
+                                  repo_name=repo_name,
+                                  repo_type=self.REPO_TYPE,
+                                  owner=owner)
         response = api_call(self, params)
 
         expected = 'Only RhodeCode admin can specify `owner` param'
@@ -874,54 +1083,132 @@
     def test_api_create_repo_exists(self):
         repo_name = self.REPO
         id_, params = _build_data(self.apikey, 'create_repo',
-                                    repo_name=repo_name,
-                                    owner=TEST_USER_ADMIN_LOGIN,
-                                    repo_type='hg',
-                                  )
+                                  repo_name=repo_name,
+                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  repo_type=self.REPO_TYPE,)
         response = api_call(self, params)
         expected = "repo `%s` already exist" % repo_name
         self._compare_error(id_, expected, given=response.body)
 
-    @mock.patch.object(RepoModel, 'create_repo', crash)
+    @mock.patch.object(RepoModel, 'create', crash)
     def test_api_create_repo_exception_occurred(self):
         repo_name = 'api-repo'
         id_, params = _build_data(self.apikey, 'create_repo',
-                                    repo_name=repo_name,
-                                    owner=TEST_USER_ADMIN_LOGIN,
-                                    repo_type='hg',
-                                  )
+                                  repo_name=repo_name,
+                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  repo_type=self.REPO_TYPE,)
         response = api_call(self, params)
         expected = 'failed to create repository `%s`' % repo_name
         self._compare_error(id_, expected, given=response.body)
 
+    @parameterized.expand([
+        ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
+        ('description', {'description': 'new description'}),
+        ('active', {'active': True}),
+        ('active', {'active': False}),
+        ('clone_uri', {'clone_uri': 'http://foo.com/repo'}),
+        ('clone_uri', {'clone_uri': None}),
+        ('landing_rev', {'landing_rev': 'branch:master'}),
+        ('enable_statistics', {'enable_statistics': True}),
+        ('enable_locking', {'enable_locking': True}),
+        ('enable_downloads', {'enable_downloads': True}),
+        ('name', {'name': 'new_repo_name'}),
+        ('repo_group', {'group': 'test_group_for_update'}),
+    ])
+    def test_api_update_repo(self, changing_attr, updates):
+        repo_name = 'api_update_me'
+        repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
+        if changing_attr == 'repo_group':
+            fixture.create_repo_group(updates['group'])
+
+        id_, params = _build_data(self.apikey, 'update_repo',
+                                  repoid=repo_name, **updates)
+        response = api_call(self, params)
+        if changing_attr == 'name':
+            repo_name = updates['name']
+        if changing_attr == 'repo_group':
+            repo_name = '/'.join([updates['group'], repo_name])
+        try:
+            expected = {
+                'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
+                'repository': repo.get_api_data()
+            }
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_repo(repo_name)
+            if changing_attr == 'repo_group':
+                fixture.destroy_repo_group(updates['group'])
+
+    def test_api_update_repo_repo_group_does_not_exist(self):
+        repo_name = 'admin_owned'
+        fixture.create_repo(repo_name)
+        updates = {'group': 'test_group_for_update'}
+        id_, params = _build_data(self.apikey, 'update_repo',
+                                  repoid=repo_name, **updates)
+        response = api_call(self, params)
+        try:
+            expected = 'repository group `%s` does not exist' % updates['group']
+            self._compare_error(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_repo(repo_name)
+
+    def test_api_update_repo_regular_user_not_allowed(self):
+        repo_name = 'admin_owned'
+        fixture.create_repo(repo_name)
+        updates = {'active': False}
+        id_, params = _build_data(self.apikey_regular, 'update_repo',
+                                  repoid=repo_name, **updates)
+        response = api_call(self, params)
+        try:
+            expected = 'repository `%s` does not exist' % repo_name
+            self._compare_error(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_repo(repo_name)
+
+    @mock.patch.object(RepoModel, 'update', crash)
+    def test_api_update_repo_exception_occured(self):
+        repo_name = 'api_update_me'
+        fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
+        id_, params = _build_data(self.apikey, 'update_repo',
+                                  repoid=repo_name, owner=TEST_USER_ADMIN_LOGIN,)
+        response = api_call(self, params)
+        try:
+            expected = 'failed to update repo `%s`' % repo_name
+            self._compare_error(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_repo(repo_name)
+
     def test_api_delete_repo(self):
         repo_name = 'api_delete_me'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
 
         id_, params = _build_data(self.apikey, 'delete_repo',
-                                  repoid=repo_name,)
+                                  repoid=repo_name, )
         response = api_call(self, params)
 
         ret = {
             'msg': 'Deleted repository `%s`' % repo_name,
             'success': True
         }
-        expected = ret
-        self._compare_ok(id_, expected, given=response.body)
+        try:
+            expected = ret
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_repo(repo_name)
 
     def test_api_delete_repo_by_non_admin(self):
         repo_name = 'api_delete_me'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
                             cur_user=self.TEST_USER_LOGIN)
-        try:
-            id_, params = _build_data(self.apikey_regular, 'delete_repo',
-                                      repoid=repo_name,)
-            response = api_call(self, params)
+        id_, params = _build_data(self.apikey_regular, 'delete_repo',
+                                  repoid=repo_name, )
+        response = api_call(self, params)
 
-            ret = {
-                'msg': 'Deleted repository `%s`' % repo_name,
-                'success': True
-            }
+        ret = {
+            'msg': 'Deleted repository `%s`' % repo_name,
+            'success': True
+        }
+        try:
             expected = ret
             self._compare_ok(id_, expected, given=response.body)
         finally:
@@ -932,7 +1219,7 @@
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
         try:
             id_, params = _build_data(self.apikey_regular, 'delete_repo',
-                                      repoid=repo_name,)
+                                      repoid=repo_name, )
             response = api_call(self, params)
             expected = 'repository `%s` does not exist' % (repo_name)
             self._compare_error(id_, expected, given=response.body)
@@ -945,7 +1232,7 @@
         try:
             with mock.patch.object(RepoModel, 'delete', crash):
                 id_, params = _build_data(self.apikey, 'delete_repo',
-                                          repoid=repo_name,)
+                                          repoid=repo_name, )
                 response = api_call(self, params)
 
                 expected = 'failed to delete repository `%s`' % repo_name
@@ -956,16 +1243,17 @@
     def test_api_fork_repo(self):
         fork_name = 'api-repo-fork'
         id_, params = _build_data(self.apikey, 'fork_repo',
-                                    repoid=self.REPO,
-                                    fork_name=fork_name,
-                                    owner=TEST_USER_ADMIN_LOGIN,
-                                  )
+                                  repoid=self.REPO,
+                                  fork_name=fork_name,
+                                  owner=TEST_USER_ADMIN_LOGIN,
+        )
         response = api_call(self, params)
 
         ret = {
             'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
                                                      fork_name),
-            'success': True
+            'success': True,
+            'task': None,
         }
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
@@ -974,15 +1262,16 @@
     def test_api_fork_repo_non_admin(self):
         fork_name = 'api-repo-fork'
         id_, params = _build_data(self.apikey_regular, 'fork_repo',
-                                    repoid=self.REPO,
-                                    fork_name=fork_name,
-                                  )
+                                  repoid=self.REPO,
+                                  fork_name=fork_name,
+        )
         response = api_call(self, params)
 
         ret = {
             'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
                                                      fork_name),
-            'success': True
+            'success': True,
+            'task': None,
         }
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
@@ -991,10 +1280,10 @@
     def test_api_fork_repo_non_admin_specify_owner(self):
         fork_name = 'api-repo-fork'
         id_, params = _build_data(self.apikey_regular, 'fork_repo',
-                                    repoid=self.REPO,
-                                    fork_name=fork_name,
-                                    owner=TEST_USER_ADMIN_LOGIN,
-                                  )
+                                  repoid=self.REPO,
+                                  fork_name=fork_name,
+                                  owner=TEST_USER_ADMIN_LOGIN,
+        )
         response = api_call(self, params)
         expected = 'Only RhodeCode admin can specify `owner` param'
         self._compare_error(id_, expected, given=response.body)
@@ -1006,9 +1295,9 @@
                                           perm='repository.none')
         fork_name = 'api-repo-fork'
         id_, params = _build_data(self.apikey_regular, 'fork_repo',
-                                    repoid=self.REPO,
-                                    fork_name=fork_name,
-                                  )
+                                  repoid=self.REPO,
+                                  fork_name=fork_name,
+        )
         response = api_call(self, params)
         expected = 'repository `%s` does not exist' % (self.REPO)
         self._compare_error(id_, expected, given=response.body)
@@ -1018,10 +1307,10 @@
         fork_name = 'api-repo-fork'
         owner = 'i-dont-exist'
         id_, params = _build_data(self.apikey, 'fork_repo',
-                                    repoid=self.REPO,
-                                    fork_name=fork_name,
-                                    owner=owner,
-                                  )
+                                  repoid=self.REPO,
+                                  fork_name=fork_name,
+                                  owner=owner,
+        )
         response = api_call(self, params)
         expected = 'user `%s` does not exist' % owner
         self._compare_error(id_, expected, given=response.body)
@@ -1034,10 +1323,10 @@
             fork_name = 'api-repo-fork'
 
             id_, params = _build_data(self.apikey, 'fork_repo',
-                                        repoid=self.REPO,
-                                        fork_name=fork_name,
-                                        owner=TEST_USER_ADMIN_LOGIN,
-                                      )
+                                      repoid=self.REPO,
+                                      fork_name=fork_name,
+                                      owner=TEST_USER_ADMIN_LOGIN,
+            )
             response = api_call(self, params)
 
             expected = "fork `%s` already exist" % fork_name
@@ -1049,10 +1338,10 @@
         fork_name = self.REPO
 
         id_, params = _build_data(self.apikey, 'fork_repo',
-                                    repoid=self.REPO,
-                                    fork_name=fork_name,
-                                    owner=TEST_USER_ADMIN_LOGIN,
-                                  )
+                                  repoid=self.REPO,
+                                  fork_name=fork_name,
+                                  owner=TEST_USER_ADMIN_LOGIN,
+        )
         response = api_call(self, params)
 
         expected = "repo `%s` already exist" % fork_name
@@ -1062,68 +1351,68 @@
     def test_api_fork_repo_exception_occurred(self):
         fork_name = 'api-repo-fork'
         id_, params = _build_data(self.apikey, 'fork_repo',
-                                    repoid=self.REPO,
-                                    fork_name=fork_name,
-                                    owner=TEST_USER_ADMIN_LOGIN,
-                                  )
+                                  repoid=self.REPO,
+                                  fork_name=fork_name,
+                                  owner=TEST_USER_ADMIN_LOGIN,
+        )
         response = api_call(self, params)
 
         expected = 'failed to fork repository `%s` as `%s`' % (self.REPO,
                                                                fork_name)
         self._compare_error(id_, expected, given=response.body)
 
-    def test_api_get_users_group(self):
-        id_, params = _build_data(self.apikey, 'get_users_group',
-                                  usersgroupid=TEST_USER_GROUP)
+    def test_api_get_user_group(self):
+        id_, params = _build_data(self.apikey, 'get_user_group',
+                                  usergroupid=TEST_USER_GROUP)
         response = api_call(self, params)
 
-        users_group = UserGroupModel().get_group(TEST_USER_GROUP)
+        user_group = UserGroupModel().get_group(TEST_USER_GROUP)
         members = []
-        for user in users_group.members:
+        for user in user_group.members:
             user = user.user
             members.append(user.get_api_data())
 
-        ret = users_group.get_api_data()
+        ret = user_group.get_api_data()
         ret['members'] = members
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
-    def test_api_get_users_groups(self):
+    def test_api_get_user_groups(self):
+        gr_name = 'test_user_group2'
+        make_user_group(gr_name)
 
-        make_users_group('test_users_group2')
-
-        id_, params = _build_data(self.apikey, 'get_users_groups',)
+        id_, params = _build_data(self.apikey, 'get_user_groups', )
         response = api_call(self, params)
 
-        expected = []
-        for gr_name in [TEST_USER_GROUP, 'test_users_group2']:
-            users_group = UserGroupModel().get_group(gr_name)
-            ret = users_group.get_api_data()
-            expected.append(ret)
-        self._compare_ok(id_, expected, given=response.body)
+        try:
+            expected = []
+            for gr_name in [TEST_USER_GROUP, 'test_user_group2']:
+                user_group = UserGroupModel().get_group(gr_name)
+                ret = user_group.get_api_data()
+                expected.append(ret)
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_user_group(gr_name)
 
-        UserGroupModel().delete(users_group='test_users_group2')
-        Session().commit()
-
-    def test_api_create_users_group(self):
+    def test_api_create_user_group(self):
         group_name = 'some_new_group'
-        id_, params = _build_data(self.apikey, 'create_users_group',
+        id_, params = _build_data(self.apikey, 'create_user_group',
                                   group_name=group_name)
         response = api_call(self, params)
 
         ret = {
             'msg': 'created new user group `%s`' % group_name,
-            'users_group': jsonify(UserGroupModel()\
-                                   .get_by_name(group_name)\
-                                   .get_api_data())
+            'user_group': jsonify(UserGroupModel() \
+                .get_by_name(group_name) \
+                .get_api_data())
         }
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
-        destroy_users_group(group_name)
+        fixture.destroy_user_group(group_name)
 
-    def test_api_get_users_group_that_exist(self):
-        id_, params = _build_data(self.apikey, 'create_users_group',
+    def test_api_get_user_group_that_exist(self):
+        id_, params = _build_data(self.apikey, 'create_user_group',
                                   group_name=TEST_USER_GROUP)
         response = api_call(self, params)
 
@@ -1131,36 +1420,72 @@
         self._compare_error(id_, expected, given=response.body)
 
     @mock.patch.object(UserGroupModel, 'create', crash)
-    def test_api_get_users_group_exception_occurred(self):
+    def test_api_get_user_group_exception_occurred(self):
         group_name = 'exception_happens'
-        id_, params = _build_data(self.apikey, 'create_users_group',
+        id_, params = _build_data(self.apikey, 'create_user_group',
                                   group_name=group_name)
         response = api_call(self, params)
 
         expected = 'failed to create group `%s`' % group_name
         self._compare_error(id_, expected, given=response.body)
 
-    def test_api_add_user_to_users_group(self):
+    @parameterized.expand([('group_name', {'group_name': 'new_group_name'}),
+                           ('group_name', {'group_name': 'test_group_for_update'}),
+                           ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
+                           ('active', {'active': False}),
+                           ('active', {'active': True})])
+    def test_api_update_user_group(self, changing_attr, updates):
+        gr_name = 'test_group_for_update'
+        user_group = fixture.create_user_group(gr_name)
+        id_, params = _build_data(self.apikey, 'update_user_group',
+                                  usergroupid=gr_name, **updates)
+        response = api_call(self, params)
+        try:
+            expected = {
+               'msg': 'updated user group ID:%s %s' % (user_group.users_group_id,
+                                                     user_group.users_group_name),
+               'user_group': user_group.get_api_data()
+            }
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            if changing_attr == 'group_name':
+                # switch to updated name for proper cleanup
+                gr_name = updates['group_name']
+            fixture.destroy_user_group(gr_name)
+
+    @mock.patch.object(UserGroupModel, 'update', crash)
+    def test_api_update_user_group_exception_occured(self):
         gr_name = 'test_group'
         fixture.create_user_group(gr_name)
-        id_, params = _build_data(self.apikey, 'add_user_to_users_group',
-                                  usersgroupid=gr_name,
+        id_, params = _build_data(self.apikey, 'update_user_group',
+                                  usergroupid=gr_name)
+        response = api_call(self, params)
+        try:
+            expected = 'failed to update user group `%s`' % gr_name
+            self._compare_error(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_user_group(gr_name)
+
+    def test_api_add_user_to_user_group(self):
+        gr_name = 'test_group'
+        fixture.create_user_group(gr_name)
+        id_, params = _build_data(self.apikey, 'add_user_to_user_group',
+                                  usergroupid=gr_name,
                                   userid=TEST_USER_ADMIN_LOGIN)
         response = api_call(self, params)
+        try:
+            expected = {
+            'msg': 'added member `%s` to user group `%s`' % (
+                    TEST_USER_ADMIN_LOGIN, gr_name),
+            'success': True
+            }
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_user_group(gr_name)
 
-        expected = {
-                    'msg': 'added member `%s` to user group `%s`' % (
-                                TEST_USER_ADMIN_LOGIN, gr_name
-                            ),
-                    'success': True}
-        self._compare_ok(id_, expected, given=response.body)
-
-        UserGroupModel().delete(users_group=gr_name)
-        Session().commit()
-
-    def test_api_add_user_to_users_group_that_doesnt_exist(self):
-        id_, params = _build_data(self.apikey, 'add_user_to_users_group',
-                                  usersgroupid='false-group',
+    def test_api_add_user_to_user_group_that_doesnt_exist(self):
+        id_, params = _build_data(self.apikey, 'add_user_to_user_group',
+                                  usergroupid='false-group',
                                   userid=TEST_USER_ADMIN_LOGIN)
         response = api_call(self, params)
 
@@ -1168,80 +1493,136 @@
         self._compare_error(id_, expected, given=response.body)
 
     @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
-    def test_api_add_user_to_users_group_exception_occurred(self):
+    def test_api_add_user_to_user_group_exception_occurred(self):
         gr_name = 'test_group'
         fixture.create_user_group(gr_name)
-        id_, params = _build_data(self.apikey, 'add_user_to_users_group',
-                                  usersgroupid=gr_name,
+        id_, params = _build_data(self.apikey, 'add_user_to_user_group',
+                                  usergroupid=gr_name,
                                   userid=TEST_USER_ADMIN_LOGIN)
         response = api_call(self, params)
 
-        expected = 'failed to add member to user group `%s`' % gr_name
-        self._compare_error(id_, expected, given=response.body)
+        try:
+            expected = 'failed to add member to user group `%s`' % gr_name
+            self._compare_error(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_user_group(gr_name)
 
-        UserGroupModel().delete(users_group=gr_name)
+    def test_api_remove_user_from_user_group(self):
+        gr_name = 'test_group_3'
+        gr = fixture.create_user_group(gr_name)
+        UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
         Session().commit()
+        id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
+                                  usergroupid=gr_name,
+                                  userid=TEST_USER_ADMIN_LOGIN)
+        response = api_call(self, params)
 
-    def test_api_remove_user_from_users_group(self):
+        try:
+            expected = {
+                'msg': 'removed member `%s` from user group `%s`' % (
+                    TEST_USER_ADMIN_LOGIN, gr_name
+                ),
+                'success': True}
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_user_group(gr_name)
+
+    @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
+    def test_api_remove_user_from_user_group_exception_occurred(self):
         gr_name = 'test_group_3'
         gr = fixture.create_user_group(gr_name)
         UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
         Session().commit()
-        id_, params = _build_data(self.apikey, 'remove_user_from_users_group',
-                                  usersgroupid=gr_name,
+        id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
+                                  usergroupid=gr_name,
+                                  userid=TEST_USER_ADMIN_LOGIN)
+        response = api_call(self, params)
+        try:
+            expected = 'failed to remove member from user group `%s`' % gr_name
+            self._compare_error(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_user_group(gr_name)
+
+    def test_api_delete_user_group(self):
+        gr_name = 'test_group'
+        ugroup = fixture.create_user_group(gr_name)
+        gr_id = ugroup.users_group_id
+        id_, params = _build_data(self.apikey, 'delete_user_group',
+                                  usergroupid=gr_name,
                                   userid=TEST_USER_ADMIN_LOGIN)
         response = api_call(self, params)
 
-        expected = {
-                    'msg': 'removed member `%s` from user group `%s`' % (
-                                TEST_USER_ADMIN_LOGIN, gr_name
-                            ),
-                    'success': True}
-        self._compare_ok(id_, expected, given=response.body)
+        try:
+            expected = {
+                'user_group': None,
+                'msg': 'deleted user group ID:%s %s' % (gr_id, gr_name)
+            }
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            if UserGroupModel().get_by_name(gr_name):
+                fixture.destroy_user_group(gr_name)
 
-        UserGroupModel().delete(users_group=gr_name)
-        Session().commit()
+    def test_api_delete_user_group_that_is_assigned(self):
+        gr_name = 'test_group'
+        ugroup = fixture.create_user_group(gr_name)
+        gr_id = ugroup.users_group_id
 
-    @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
-    def test_api_remove_user_from_users_group_exception_occurred(self):
-        gr_name = 'test_group_3'
-        gr = fixture.create_user_group(gr_name)
-        UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
-        Session().commit()
-        id_, params = _build_data(self.apikey, 'remove_user_from_users_group',
-                                  usersgroupid=gr_name,
+        ugr_to_perm = RepoModel().grant_user_group_permission(self.REPO, gr_name, 'repository.write')
+        msg = 'RepoGroup assigned to [%s]' % (ugr_to_perm)
+
+        id_, params = _build_data(self.apikey, 'delete_user_group',
+                                  usergroupid=gr_name,
                                   userid=TEST_USER_ADMIN_LOGIN)
         response = api_call(self, params)
 
-        expected = 'failed to remove member from user group `%s`' % gr_name
-        self._compare_error(id_, expected, given=response.body)
+        try:
+            expected = msg
+            self._compare_error(id_, expected, given=response.body)
+        finally:
+            if UserGroupModel().get_by_name(gr_name):
+                fixture.destroy_user_group(gr_name)
 
-        UserGroupModel().delete(users_group=gr_name)
-        Session().commit()
+    def test_api_delete_user_group_exception_occured(self):
+        gr_name = 'test_group'
+        ugroup = fixture.create_user_group(gr_name)
+        gr_id = ugroup.users_group_id
+        id_, params = _build_data(self.apikey, 'delete_user_group',
+                                  usergroupid=gr_name,
+                                  userid=TEST_USER_ADMIN_LOGIN)
+
+        try:
+            with mock.patch.object(UserGroupModel, 'delete', crash):
+                response = api_call(self, params)
+                expected = 'failed to delete user group ID:%s %s' % (gr_id, gr_name)
+                self._compare_error(id_, expected, given=response.body)
+        finally:
+            fixture.destroy_user_group(gr_name)
 
     @parameterized.expand([('none', 'repository.none'),
                            ('read', 'repository.read'),
                            ('write', 'repository.write'),
                            ('admin', 'repository.admin')])
     def test_api_grant_user_permission(self, name, perm):
-        id_, params = _build_data(self.apikey, 'grant_user_permission',
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_permission',
                                   repoid=self.REPO,
                                   userid=TEST_USER_ADMIN_LOGIN,
                                   perm=perm)
         response = api_call(self, params)
 
         ret = {
-                'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
-                    perm, TEST_USER_ADMIN_LOGIN, self.REPO
-                ),
-                'success': True
-            }
+            'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
+                perm, TEST_USER_ADMIN_LOGIN, self.REPO
+            ),
+            'success': True
+        }
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
     def test_api_grant_user_permission_wrong_permission(self):
         perm = 'haha.no.permission'
-        id_, params = _build_data(self.apikey, 'grant_user_permission',
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_permission',
                                   repoid=self.REPO,
                                   userid=TEST_USER_ADMIN_LOGIN,
                                   perm=perm)
@@ -1253,21 +1634,23 @@
     @mock.patch.object(RepoModel, 'grant_user_permission', crash)
     def test_api_grant_user_permission_exception_when_adding(self):
         perm = 'repository.read'
-        id_, params = _build_data(self.apikey, 'grant_user_permission',
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_permission',
                                   repoid=self.REPO,
                                   userid=TEST_USER_ADMIN_LOGIN,
                                   perm=perm)
         response = api_call(self, params)
 
         expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
-                    TEST_USER_ADMIN_LOGIN, self.REPO
-                )
+            TEST_USER_ADMIN_LOGIN, self.REPO
+        )
         self._compare_error(id_, expected, given=response.body)
 
     def test_api_revoke_user_permission(self):
-        id_, params = _build_data(self.apikey, 'revoke_user_permission',
+        id_, params = _build_data(self.apikey,
+                                  'revoke_user_permission',
                                   repoid=self.REPO,
-                                  userid=TEST_USER_ADMIN_LOGIN,)
+                                  userid=TEST_USER_ADMIN_LOGIN, )
         response = api_call(self, params)
 
         expected = {
@@ -1280,24 +1663,26 @@
 
     @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
     def test_api_revoke_user_permission_exception_when_adding(self):
-        id_, params = _build_data(self.apikey, 'revoke_user_permission',
+        id_, params = _build_data(self.apikey,
+                                  'revoke_user_permission',
                                   repoid=self.REPO,
-                                  userid=TEST_USER_ADMIN_LOGIN,)
+                                  userid=TEST_USER_ADMIN_LOGIN, )
         response = api_call(self, params)
 
         expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
-                    TEST_USER_ADMIN_LOGIN, self.REPO
-                )
+            TEST_USER_ADMIN_LOGIN, self.REPO
+        )
         self._compare_error(id_, expected, given=response.body)
 
     @parameterized.expand([('none', 'repository.none'),
                            ('read', 'repository.read'),
                            ('write', 'repository.write'),
                            ('admin', 'repository.admin')])
-    def test_api_grant_users_group_permission(self, name, perm):
-        id_, params = _build_data(self.apikey, 'grant_users_group_permission',
+    def test_api_grant_user_group_permission(self, name, perm):
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_group_permission',
                                   repoid=self.REPO,
-                                  usersgroupid=TEST_USER_GROUP,
+                                  usergroupid=TEST_USER_GROUP,
                                   perm=perm)
         response = api_call(self, params)
 
@@ -1310,39 +1695,42 @@
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
-    def test_api_grant_users_group_permission_wrong_permission(self):
+    def test_api_grant_user_group_permission_wrong_permission(self):
         perm = 'haha.no.permission'
-        id_, params = _build_data(self.apikey, 'grant_users_group_permission',
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_group_permission',
                                   repoid=self.REPO,
-                                  usersgroupid=TEST_USER_GROUP,
+                                  usergroupid=TEST_USER_GROUP,
                                   perm=perm)
         response = api_call(self, params)
 
         expected = 'permission `%s` does not exist' % perm
         self._compare_error(id_, expected, given=response.body)
 
-    @mock.patch.object(RepoModel, 'grant_users_group_permission', crash)
-    def test_api_grant_users_group_permission_exception_when_adding(self):
+    @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
+    def test_api_grant_user_group_permission_exception_when_adding(self):
         perm = 'repository.read'
-        id_, params = _build_data(self.apikey, 'grant_users_group_permission',
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_group_permission',
                                   repoid=self.REPO,
-                                  usersgroupid=TEST_USER_GROUP,
+                                  usergroupid=TEST_USER_GROUP,
                                   perm=perm)
         response = api_call(self, params)
 
         expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
-                    TEST_USER_GROUP, self.REPO
-                )
+            TEST_USER_GROUP, self.REPO
+        )
         self._compare_error(id_, expected, given=response.body)
 
-    def test_api_revoke_users_group_permission(self):
-        RepoModel().grant_users_group_permission(repo=self.REPO,
-                                                 group_name=TEST_USER_GROUP,
-                                                 perm='repository.read')
+    def test_api_revoke_user_group_permission(self):
+        RepoModel().grant_user_group_permission(repo=self.REPO,
+                                                group_name=TEST_USER_GROUP,
+                                                perm='repository.read')
         Session().commit()
-        id_, params = _build_data(self.apikey, 'revoke_users_group_permission',
+        id_, params = _build_data(self.apikey,
+                                  'revoke_user_group_permission',
                                   repoid=self.REPO,
-                                  usersgroupid=TEST_USER_GROUP,)
+                                  usergroupid=TEST_USER_GROUP, )
         response = api_call(self, params)
 
         expected = {
@@ -1353,15 +1741,555 @@
         }
         self._compare_ok(id_, expected, given=response.body)
 
-    @mock.patch.object(RepoModel, 'revoke_users_group_permission', crash)
-    def test_api_revoke_users_group_permission_exception_when_adding(self):
-
-        id_, params = _build_data(self.apikey, 'revoke_users_group_permission',
+    @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
+    def test_api_revoke_user_group_permission_exception_when_adding(self):
+        id_, params = _build_data(self.apikey,
+                                  'revoke_user_group_permission',
                                   repoid=self.REPO,
-                                  usersgroupid=TEST_USER_GROUP,)
+                                  usergroupid=TEST_USER_GROUP, )
         response = api_call(self, params)
 
         expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
-                    TEST_USER_GROUP, self.REPO
-                )
+            TEST_USER_GROUP, self.REPO
+        )
+        self._compare_error(id_, expected, given=response.body)
+
+    @parameterized.expand([
+        ('none', 'group.none', 'none'),
+        ('read', 'group.read', 'none'),
+        ('write', 'group.write', 'none'),
+        ('admin', 'group.admin', 'none'),
+
+        ('none', 'group.none', 'all'),
+        ('read', 'group.read', 'all'),
+        ('write', 'group.write', 'all'),
+        ('admin', 'group.admin', 'all'),
+
+        ('none', 'group.none', 'repos'),
+        ('read', 'group.read', 'repos'),
+        ('write', 'group.write', 'repos'),
+        ('admin', 'group.admin', 'repos'),
+
+        ('none', 'group.none', 'groups'),
+        ('read', 'group.read', 'groups'),
+        ('write', 'group.write', 'groups'),
+        ('admin', 'group.admin', 'groups'),
+    ])
+    def test_api_grant_user_permission_to_repo_group(self, name, perm, apply_to_children):
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_permission_to_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  perm=perm, apply_to_children=apply_to_children)
+        response = api_call(self, params)
+
+        ret = {
+            'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
+                perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+            ),
+            'success': True
+        }
+        expected = ret
+        self._compare_ok(id_, expected, given=response.body)
+
+    @parameterized.expand([
+        ('none_fails', 'group.none', 'none', False, False),
+        ('read_fails', 'group.read', 'none', False, False),
+        ('write_fails', 'group.write', 'none', False, False),
+        ('admin_fails', 'group.admin', 'none', False, False),
+
+        # with granted perms
+        ('none_ok', 'group.none', 'none', True, True),
+        ('read_ok', 'group.read', 'none', True, True),
+        ('write_ok', 'group.write', 'none', True, True),
+        ('admin_ok', 'group.admin', 'none', True, True),
+    ])
+    def test_api_grant_user_permission_to_repo_group_by_regular_user(
+            self, name, perm, apply_to_children, grant_admin, access_ok):
+        if grant_admin:
+            RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
+                                                   self.TEST_USER_LOGIN,
+                                                   'group.admin')
+            Session().commit()
+
+        id_, params = _build_data(self.apikey_regular,
+                                  'grant_user_permission_to_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  perm=perm, apply_to_children=apply_to_children)
+        response = api_call(self, params)
+        if access_ok:
+            ret = {
+                'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
+                    perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+                ),
+                'success': True
+            }
+            expected = ret
+            self._compare_ok(id_, expected, given=response.body)
+        else:
+            expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
+            self._compare_error(id_, expected, given=response.body)
+
+    def test_api_grant_user_permission_to_repo_group_wrong_permission(self):
+        perm = 'haha.no.permission'
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_permission_to_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  perm=perm)
+        response = api_call(self, params)
+
+        expected = 'permission `%s` does not exist' % perm
+        self._compare_error(id_, expected, given=response.body)
+
+    @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
+    def test_api_grant_user_permission_to_repo_group_exception_when_adding(self):
+        perm = 'group.read'
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_permission_to_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  perm=perm)
+        response = api_call(self, params)
+
+        expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
+            TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+        )
+        self._compare_error(id_, expected, given=response.body)
+
+    @parameterized.expand([
+        ('none', 'none'),
+        ('all', 'all'),
+        ('repos', 'repos'),
+        ('groups', 'groups'),
+    ])
+    def test_api_revoke_user_permission_from_repo_group(self, name, apply_to_children):
+        RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
+                                               user=TEST_USER_ADMIN_LOGIN,
+                                               perm='group.read',)
+        Session().commit()
+
+        id_, params = _build_data(self.apikey,
+                                  'revoke_user_permission_from_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  apply_to_children=apply_to_children,)
+        response = api_call(self, params)
+
+        expected = {
+            'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
+                apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+            ),
+            'success': True
+        }
+        self._compare_ok(id_, expected, given=response.body)
+
+    @parameterized.expand([
+        ('none', 'none', False, False),
+        ('all', 'all', False, False),
+        ('repos', 'repos', False, False),
+        ('groups', 'groups', False, False),
+
+        # after granting admin rights
+        ('none', 'none', False, False),
+        ('all', 'all', False, False),
+        ('repos', 'repos', False, False),
+        ('groups', 'groups', False, False),
+    ])
+    def test_api_revoke_user_permission_from_repo_group_by_regular_user(
+            self, name, apply_to_children, grant_admin, access_ok):
+        RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
+                                               user=TEST_USER_ADMIN_LOGIN,
+                                               perm='group.read',)
+        Session().commit()
+
+        if grant_admin:
+            RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
+                                                   self.TEST_USER_LOGIN,
+                                                   'group.admin')
+            Session().commit()
+
+        id_, params = _build_data(self.apikey_regular,
+                                  'revoke_user_permission_from_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  apply_to_children=apply_to_children,)
+        response = api_call(self, params)
+        if access_ok:
+            expected = {
+                'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
+                    apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+                ),
+                'success': True
+            }
+            self._compare_ok(id_, expected, given=response.body)
+        else:
+            expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
+            self._compare_error(id_, expected, given=response.body)
+
+    @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
+    def test_api_revoke_user_permission_from_repo_group_exception_when_adding(self):
+        id_, params = _build_data(self.apikey,
+                                  'revoke_user_permission_from_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  userid=TEST_USER_ADMIN_LOGIN, )
+        response = api_call(self, params)
+
+        expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
+            TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+        )
         self._compare_error(id_, expected, given=response.body)
+
+    @parameterized.expand([
+        ('none', 'group.none', 'none'),
+        ('read', 'group.read', 'none'),
+        ('write', 'group.write', 'none'),
+        ('admin', 'group.admin', 'none'),
+
+        ('none', 'group.none', 'all'),
+        ('read', 'group.read', 'all'),
+        ('write', 'group.write', 'all'),
+        ('admin', 'group.admin', 'all'),
+
+        ('none', 'group.none', 'repos'),
+        ('read', 'group.read', 'repos'),
+        ('write', 'group.write', 'repos'),
+        ('admin', 'group.admin', 'repos'),
+
+        ('none', 'group.none', 'groups'),
+        ('read', 'group.read', 'groups'),
+        ('write', 'group.write', 'groups'),
+        ('admin', 'group.admin', 'groups'),
+    ])
+    def test_api_grant_user_group_permission_to_repo_group(self, name, perm, apply_to_children):
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_group_permission_to_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  usergroupid=TEST_USER_GROUP,
+                                  perm=perm,
+                                  apply_to_children=apply_to_children,)
+        response = api_call(self, params)
+
+        ret = {
+            'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
+                perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
+            ),
+            'success': True
+        }
+        expected = ret
+        self._compare_ok(id_, expected, given=response.body)
+
+    @parameterized.expand([
+        ('none_fails', 'group.none', 'none', False, False),
+        ('read_fails', 'group.read', 'none', False, False),
+        ('write_fails', 'group.write', 'none', False, False),
+        ('admin_fails', 'group.admin', 'none', False, False),
+
+        # with granted perms
+        ('none_ok', 'group.none', 'none', True, True),
+        ('read_ok', 'group.read', 'none', True, True),
+        ('write_ok', 'group.write', 'none', True, True),
+        ('admin_ok', 'group.admin', 'none', True, True),
+    ])
+    def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
+            self, name, perm, apply_to_children, grant_admin, access_ok):
+        if grant_admin:
+            RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
+                                                   self.TEST_USER_LOGIN,
+                                                   'group.admin')
+            Session().commit()
+
+        id_, params = _build_data(self.apikey_regular,
+                                  'grant_user_group_permission_to_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  usergroupid=TEST_USER_GROUP,
+                                  perm=perm,
+                                  apply_to_children=apply_to_children,)
+        response = api_call(self, params)
+        if access_ok:
+            ret = {
+                'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
+                    perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
+                ),
+                'success': True
+            }
+            expected = ret
+            self._compare_ok(id_, expected, given=response.body)
+        else:
+            expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
+            self._compare_error(id_, expected, given=response.body)
+
+    def test_api_grant_user_group_permission_to_repo_group_wrong_permission(self):
+        perm = 'haha.no.permission'
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_group_permission_to_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  usergroupid=TEST_USER_GROUP,
+                                  perm=perm)
+        response = api_call(self, params)
+
+        expected = 'permission `%s` does not exist' % perm
+        self._compare_error(id_, expected, given=response.body)
+
+    @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
+    def test_api_grant_user_group_permission_exception_when_adding(self):
+        perm = 'group.read'
+        id_, params = _build_data(self.apikey,
+                                  'grant_user_group_permission_to_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  usergroupid=TEST_USER_GROUP,
+                                  perm=perm)
+        response = api_call(self, params)
+
+        expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
+            TEST_USER_GROUP, TEST_REPO_GROUP
+        )
+        self._compare_error(id_, expected, given=response.body)
+
+    @parameterized.expand([
+        ('none', 'none'),
+        ('all', 'all'),
+        ('repos', 'repos'),
+        ('groups', 'groups'),
+    ])
+    def test_api_revoke_user_group_permission_from_repo_group(self, name, apply_to_children):
+        RepoGroupModel().grant_user_group_permission(repo_group=TEST_REPO_GROUP,
+                                                     group_name=TEST_USER_GROUP,
+                                                     perm='group.read',)
+        Session().commit()
+        id_, params = _build_data(self.apikey,
+                                  'revoke_user_group_permission_from_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  usergroupid=TEST_USER_GROUP,
+                                  apply_to_children=apply_to_children,)
+        response = api_call(self, params)
+
+        expected = {
+            'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
+                apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
+            ),
+            'success': True
+        }
+        self._compare_ok(id_, expected, given=response.body)
+
+    @parameterized.expand([
+        ('none', 'none', False, False),
+        ('all', 'all', False, False),
+        ('repos', 'repos', False, False),
+        ('groups', 'groups', False, False),
+
+        # after granting admin rights
+        ('none', 'none', False, False),
+        ('all', 'all', False, False),
+        ('repos', 'repos', False, False),
+        ('groups', 'groups', False, False),
+    ])
+    def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
+            self, name, apply_to_children, grant_admin, access_ok):
+        RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
+                                               user=TEST_USER_ADMIN_LOGIN,
+                                               perm='group.read',)
+        Session().commit()
+
+        if grant_admin:
+            RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
+                                                   self.TEST_USER_LOGIN,
+                                                   'group.admin')
+            Session().commit()
+
+        id_, params = _build_data(self.apikey_regular,
+                                  'revoke_user_group_permission_from_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  usergroupid=TEST_USER_GROUP,
+                                  apply_to_children=apply_to_children,)
+        response = api_call(self, params)
+        if access_ok:
+            expected = {
+                'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
+                    apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+                ),
+                'success': True
+            }
+            self._compare_ok(id_, expected, given=response.body)
+        else:
+            expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
+            self._compare_error(id_, expected, given=response.body)
+
+    @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
+    def test_api_revoke_user_group_permission_from_repo_group_exception_when_adding(self):
+        id_, params = _build_data(self.apikey, 'revoke_user_group_permission_from_repo_group',
+                                  repogroupid=TEST_REPO_GROUP,
+                                  usergroupid=TEST_USER_GROUP,)
+        response = api_call(self, params)
+
+        expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
+            TEST_USER_GROUP, TEST_REPO_GROUP
+        )
+        self._compare_error(id_, expected, given=response.body)
+
+    def test_api_get_gist(self):
+        gist = fixture.create_gist()
+        gist_id = gist.gist_access_id
+        gist_created_on = gist.created_on
+        id_, params = _build_data(self.apikey, 'get_gist',
+                                  gistid=gist_id, )
+        response = api_call(self, params)
+
+        expected = {
+            'access_id': gist_id,
+            'created_on': gist_created_on,
+            'description': 'new-gist',
+            'expires': -1.0,
+            'gist_id': int(gist_id),
+            'type': 'public',
+            'url': 'http://localhost:80/_admin/gists/%s' % gist_id
+        }
+
+        self._compare_ok(id_, expected, given=response.body)
+
+    def test_api_get_gist_that_does_not_exist(self):
+        id_, params = _build_data(self.apikey_regular, 'get_gist',
+                                  gistid='12345', )
+        response = api_call(self, params)
+        expected = 'gist `%s` does not exist' % ('12345',)
+        self._compare_error(id_, expected, given=response.body)
+
+    def test_api_get_gist_private_gist_without_permission(self):
+        gist = fixture.create_gist()
+        gist_id = gist.gist_access_id
+        gist_created_on = gist.created_on
+        id_, params = _build_data(self.apikey_regular, 'get_gist',
+                                  gistid=gist_id, )
+        response = api_call(self, params)
+
+        expected = 'gist `%s` does not exist' % gist_id
+        self._compare_error(id_, expected, given=response.body)
+
+    def test_api_get_gists(self):
+        fixture.create_gist()
+        fixture.create_gist()
+
+        id_, params = _build_data(self.apikey, 'get_gists')
+        response = api_call(self, params)
+        expected = response.json
+        self.assertEqual(len(response.json['result']), 2)
+        #self._compare_ok(id_, expected, given=response.body)
+
+    def test_api_get_gists_regular_user(self):
+        # by admin
+        fixture.create_gist()
+        fixture.create_gist()
+
+        # by reg user
+        fixture.create_gist(owner=self.TEST_USER_LOGIN)
+        fixture.create_gist(owner=self.TEST_USER_LOGIN)
+        fixture.create_gist(owner=self.TEST_USER_LOGIN)
+
+        id_, params = _build_data(self.apikey_regular, 'get_gists')
+        response = api_call(self, params)
+        expected = response.json
+        self.assertEqual(len(response.json['result']), 3)
+        #self._compare_ok(id_, expected, given=response.body)
+
+    def test_api_get_gists_only_for_regular_user(self):
+        # by admin
+        fixture.create_gist()
+        fixture.create_gist()
+
+        # by reg user
+        fixture.create_gist(owner=self.TEST_USER_LOGIN)
+        fixture.create_gist(owner=self.TEST_USER_LOGIN)
+        fixture.create_gist(owner=self.TEST_USER_LOGIN)
+
+        id_, params = _build_data(self.apikey, 'get_gists',
+                                  userid=self.TEST_USER_LOGIN)
+        response = api_call(self, params)
+        expected = response.json
+        self.assertEqual(len(response.json['result']), 3)
+        #self._compare_ok(id_, expected, given=response.body)
+
+    def test_api_get_gists_regular_user_with_different_userid(self):
+        id_, params = _build_data(self.apikey_regular, 'get_gists',
+                                  userid=TEST_USER_ADMIN_LOGIN)
+        response = api_call(self, params)
+        expected = 'userid is not the same as your user'
+        self._compare_error(id_, expected, given=response.body)
+
+    def test_api_create_gist(self):
+        id_, params = _build_data(self.apikey_regular, 'create_gist',
+                                  lifetime=10,
+                                  description='foobar-gist',
+                                  gist_type='public',
+                                  files={'foobar': {'content': 'foo'}})
+        response = api_call(self, params)
+        response_json = response.json
+        expected = {
+            'gist': {
+                'access_id': response_json['result']['gist']['access_id'],
+                'created_on': response_json['result']['gist']['created_on'],
+                'description': 'foobar-gist',
+                'expires': response_json['result']['gist']['expires'],
+                'gist_id': response_json['result']['gist']['gist_id'],
+                'type': 'public',
+                'url': response_json['result']['gist']['url']
+            },
+            'msg': 'created new gist'
+        }
+        self._compare_ok(id_, expected, given=response.body)
+
+    @mock.patch.object(GistModel, 'create', crash)
+    def test_api_create_gist_exception_occured(self):
+        id_, params = _build_data(self.apikey_regular, 'create_gist',
+                                  files={})
+        response = api_call(self, params)
+        expected = 'failed to create gist'
+        self._compare_error(id_, expected, given=response.body)
+
+    def test_api_delete_gist(self):
+        gist_id = fixture.create_gist().gist_access_id
+        id_, params = _build_data(self.apikey, 'delete_gist',
+                                  gistid=gist_id)
+        response = api_call(self, params)
+        expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
+        self._compare_ok(id_, expected, given=response.body)
+
+    def test_api_delete_gist_regular_user(self):
+        gist_id = fixture.create_gist(owner=self.TEST_USER_LOGIN).gist_access_id
+        id_, params = _build_data(self.apikey_regular, 'delete_gist',
+                                  gistid=gist_id)
+        response = api_call(self, params)
+        expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
+        self._compare_ok(id_, expected, given=response.body)
+
+    def test_api_delete_gist_regular_user_no_permission(self):
+        gist_id = fixture.create_gist().gist_access_id
+        id_, params = _build_data(self.apikey_regular, 'delete_gist',
+                                  gistid=gist_id)
+        response = api_call(self, params)
+        expected = 'gist `%s` does not exist' % (gist_id,)
+        self._compare_error(id_, expected, given=response.body)
+
+    @mock.patch.object(GistModel, 'delete', crash)
+    def test_api_delete_gist_exception_occured(self):
+        gist_id = fixture.create_gist().gist_access_id
+        id_, params = _build_data(self.apikey, 'delete_gist',
+                                  gistid=gist_id)
+        response = api_call(self, params)
+        expected = 'failed to delete gist ID:%s' % (gist_id,)
+        self._compare_error(id_, expected, given=response.body)
+
+    def test_api_get_ip(self):
+        id_, params = _build_data(self.apikey, 'get_ip')
+        response = api_call(self, params)
+        expected = {
+            'server_ip_addr': '0.0.0.0',
+            'user_ips': []
+        }
+        self._compare_ok(id_, expected, given=response.body)
+
+    def test_api_get_server_info(self):
+        id_, params = _build_data(self.apikey, 'get_server_info')
+        response = api_call(self, params)
+        expected = RhodeCodeSetting.get_server_info()
+        self._compare_ok(id_, expected, given=response.body)
--- a/rhodecode/tests/api/test_api_git.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/api/test_api_git.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,17 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 from rhodecode.tests import *
 from rhodecode.tests.api.api_base import BaseTestApi
 
--- a/rhodecode/tests/api/test_api_hg.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/api/test_api_hg.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,17 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 from rhodecode.tests import *
 from rhodecode.tests.api.api_base import BaseTestApi
 
--- a/rhodecode/tests/fixture.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/fixture.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,12 +1,37 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
 Helpers for fixture generation
 """
+import os
+import time
 from rhodecode.tests import *
 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup
 from rhodecode.model.meta import Session
 from rhodecode.model.repo import RepoModel
-from rhodecode.model.repos_group import ReposGroupModel
-from rhodecode.model.users_group import UserGroupModel
+from rhodecode.model.user import UserModel
+from rhodecode.model.repo_group import RepoGroupModel
+from rhodecode.model.user_group import UserGroupModel
+from rhodecode.model.gist import GistModel
+
+dn = os.path.dirname
+FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures')
+
+
+def error_function(*args, **kwargs):
+    raise Exception('Total Crash !')
 
 
 class Fixture(object):
@@ -14,20 +39,52 @@
     def __init__(self):
         pass
 
+    def anon_access(self, status):
+        """
+        Context process for disabling anonymous access. use like:
+        fixture = Fixture()
+        with fixture.anon_access(False):
+            #tests
+
+        after this block anon access will be set to `not status`
+        """
+
+        class context(object):
+            def __enter__(self):
+                anon = User.get_default_user()
+                anon.active = status
+                Session().add(anon)
+                Session().commit()
+                time.sleep(1.5)  # must sleep for cache (1s to expire)
+
+            def __exit__(self, exc_type, exc_val, exc_tb):
+                anon = User.get_default_user()
+                anon.active = not status
+                Session().add(anon)
+                Session().commit()
+
+        return context()
+
     def _get_repo_create_params(self, **custom):
         defs = dict(
             repo_name=None,
             repo_type='hg',
             clone_uri='',
-            repo_group='',
+            repo_group='-1',
             repo_description='DESC',
             repo_private=False,
-            repo_landing_rev='tip'
+            repo_landing_rev='rev:tip',
+            repo_copy_permissions=False,
+            repo_state=Repository.STATE_CREATED,
         )
         defs.update(custom)
         if 'repo_name_full' not in custom:
             defs.update({'repo_name_full': defs['repo_name']})
 
+        # fix the repo name if passed as repo_name_full
+        if defs['repo_name']:
+            defs['repo_name'] = defs['repo_name'].split('/')[-1]
+
         return defs
 
     def _get_group_create_params(self, **custom):
@@ -44,10 +101,28 @@
 
         return defs
 
+    def _get_user_create_params(self, name, **custom):
+        defs = dict(
+            username=name,
+            password='qweqwe',
+            email='%s+test@rhodecode.org' % name,
+            firstname='TestUser',
+            lastname='Test',
+            active=True,
+            admin=False,
+            extern_type='rhodecode',
+            extern_name=None
+        )
+        defs.update(custom)
+
+        return defs
+
     def _get_user_group_create_params(self, name, **custom):
         defs = dict(
             users_group_name=name,
+            user_group_description='DESC',
             users_group_active=True,
+            user_group_data={},
         )
         defs.update(custom)
 
@@ -60,10 +135,8 @@
             if r:
                 return r
 
-        if isinstance(kwargs.get('repos_group'), RepoGroup):
-            #TODO: rename the repos_group !
-            kwargs['repo_group'] = kwargs['repos_group'].group_id
-            del kwargs['repos_group']
+        if isinstance(kwargs.get('repo_group'), RepoGroup):
+            kwargs['repo_group'] = kwargs['repo_group'].group_id
 
         form_data = self._get_repo_create_params(repo_name=name, **kwargs)
         cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
@@ -92,11 +165,11 @@
         assert r
         return r
 
-    def destroy_repo(self, repo_name):
-        RepoModel().delete(repo_name)
+    def destroy_repo(self, repo_name, **kwargs):
+        RepoModel().delete(repo_name, **kwargs)
         Session().commit()
 
-    def create_group(self, name, **kwargs):
+    def create_repo_group(self, name, **kwargs):
         if 'skip_if_exists' in kwargs:
             del kwargs['skip_if_exists']
             gr = RepoGroup.get_by_group_name(group_name=name)
@@ -104,13 +177,34 @@
                 return gr
         form_data = self._get_group_create_params(group_name=name, **kwargs)
         owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
-        gr = ReposGroupModel().create(group_name=form_data['group_name'],
-                                 group_description=form_data['group_name'],
-                                 owner=owner, parent=form_data['group_parent_id'])
+        gr = RepoGroupModel().create(
+            group_name=form_data['group_name'],
+            group_description=form_data['group_name'],
+            owner=owner, parent=form_data['group_parent_id'])
         Session().commit()
         gr = RepoGroup.get_by_group_name(gr.group_name)
         return gr
 
+    def destroy_repo_group(self, repogroupid):
+        RepoGroupModel().delete(repogroupid)
+        Session().commit()
+
+    def create_user(self, name, **kwargs):
+        if 'skip_if_exists' in kwargs:
+            del kwargs['skip_if_exists']
+            user = User.get_by_username(name)
+            if user:
+                return user
+        form_data = self._get_user_create_params(name, **kwargs)
+        user = UserModel().create(form_data)
+        Session().commit()
+        user = User.get_by_username(user.username)
+        return user
+
+    def destroy_user(self, userid):
+        UserModel().delete(userid)
+        Session().commit()
+
     def create_user_group(self, name, **kwargs):
         if 'skip_if_exists' in kwargs:
             del kwargs['skip_if_exists']
@@ -119,8 +213,50 @@
                 return gr
         form_data = self._get_user_group_create_params(name, **kwargs)
         owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
-        user_group = UserGroupModel().create(name=form_data['users_group_name'],
-                        owner=owner, active=form_data['users_group_active'])
+        user_group = UserGroupModel().create(
+            name=form_data['users_group_name'],
+            description=form_data['user_group_description'],
+            owner=owner, active=form_data['users_group_active'],
+            group_data=form_data['user_group_data'])
         Session().commit()
         user_group = UserGroup.get_by_group_name(user_group.users_group_name)
         return user_group
+
+    def destroy_user_group(self, usergroupid):
+        UserGroupModel().delete(user_group=usergroupid, force=True)
+        Session().commit()
+
+    def create_gist(self, **kwargs):
+        form_data = {
+            'description': 'new-gist',
+            'owner': TEST_USER_ADMIN_LOGIN,
+            'gist_type': GistModel.cls.GIST_PUBLIC,
+            'lifetime': -1,
+            'gist_mapping': {'filename1.txt':{'content':'hello world'},}
+        }
+        form_data.update(kwargs)
+        gist = GistModel().create(
+            description=form_data['description'],owner=form_data['owner'],
+            gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'],
+            lifetime=form_data['lifetime']
+        )
+        Session().commit()
+
+        return gist
+
+    def destroy_gists(self, gistid=None):
+        for g in GistModel.cls.get_all():
+            if gistid:
+                if gistid == g.gist_access_id:
+                    GistModel().delete(g)
+            else:
+                GistModel().delete(g)
+        Session().commit()
+
+    def load_resource(self, resource_name, strip=True):
+        with open(os.path.join(FIXTURES, resource_name)) as f:
+            source = f.read()
+            if strip:
+                source = source.strip()
+
+        return source
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/fixtures/git_node_history_response.json	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,1 @@
+{"results": [{"text": "Changesets", "children": [{"text": "r625:ead8f28a4bc2 (None)", "id": "ead8f28a4bc2f45ecfb148a6b8a89758b9654a84"}, {"text": "r535:c093f94d6d35 (None)", "id": "c093f94d6d358f13c55a687da66c30c41cca4153"}, {"text": "r534:559f640ec08b (None)", "id": "559f640ec08b2a14c4a9ac863d8ca273545b8885"}, {"text": "r490:02a940b4ee37 (None)", "id": "02a940b4ee371ec64ef5b4c4870a5c89dc7fb98a"}, {"text": "r464:b45a4153a2d7 (None)", "id": "b45a4153a2d7adb8a78b63d35d39fac44a4320a6"}, {"text": "r460:0a54e66b9450 (None)", "id": "0a54e66b94502409074b163cd93c1233dcc0413f"}, {"text": "r457:a7bf2f6bf3d5 (None)", "id": "a7bf2f6bf3d5273da4bcd2032a891acae5a45e2b"}, {"text": "r456:7266de0154b4 (None)", "id": "7266de0154b4da7c42ba3d788876056dbf116b5a"}, {"text": "r455:666de4ee6507 (None)", "id": "666de4ee65074cd3e37ea01e75f65bd3e4c336bb"}, {"text": "r453:91acc599141c (None)", "id": "91acc599141c87f03e0e3551dcaacf4492632e58"}, {"text": "r442:40a2d5d71b75 (None)", "id": "40a2d5d71b758e7eafc84a324ed55142cba22f42"}, {"text": "r440:d1f898326327 (None)", "id": "d1f898326327e20524fe22417c22d71064fe54a1"}, {"text": "r420:162a36830c23 (None)", "id": "162a36830c23ccf1bf1873157fd0c8d0dfc7c817"}, {"text": "r345:c994f0de03b2 (None)", "id": "c994f0de03b2a0aa848a04fc2c0d7e737dba31fc"}, {"text": "r340:5d3d4d2c262e (None)", "id": "5d3d4d2c262e17b247d405feceeb09ff7408c940"}, {"text": "r334:4d4278a6390e (None)", "id": "4d4278a6390e42f4fc777ecf1b9b628e77da8e22"}, {"text": "r298:00dffb625166 (None)", "id": "00dffb62516650bc5050d818eb47ea1ca207954d"}, {"text": "r297:47b6be9a812e (None)", "id": "47b6be9a812ec3ed0384001a458a759f0f583fe2"}, {"text": "r289:1589fed841cd (None)", "id": "1589fed841cd9ef33155f8560727809ac3ada2c8"}, {"text": "r285:afafd0ee2821 (None)", "id": "afafd0ee28218ab979678213cb96e9e4dbd7359b"}, {"text": "r284:639b115ed2b0 (None)", "id": "639b115ed2b02017824005b5ae66282c6e25eba8"}, {"text": "r283:fcf7562d7305 (None)", "id": "fcf7562d7305affc94fe20dc89a34aefd2b8aa1e"}, {"text": "r256:ec8cbdb5f364 (None)", "id": "ec8cbdb5f364fce7843cbf148c3d95d86f935339"}, {"text": "r255:0d74d2e2bdf3 (None)", "id": "0d74d2e2bdf3dcd5ee9fe4fcfe9016c5c6486f35"}, {"text": "r243:6894ad7d8223 (None)", "id": "6894ad7d8223b1e6853e9fdaa2c38d3f0cef1e38"}, {"text": "r231:31b3f4b599fa (None)", "id": "31b3f4b599fae5f12cf438c73403679cdf923d75"}, {"text": "r220:3d2515dd21fb (None)", "id": "3d2515dd21fb34fe6c5d0029075a863f3e92f5f6"}, {"text": "r186:f804e27aa496 (None)", "id": "f804e27aa4961f2e327f2a10ee373235df20ee21"}, {"text": "r182:7f00513785a1 (None)", "id": "7f00513785a13f273a4387ef086bb795b37f013c"}, {"text": "r181:6efcdc61028c (None)", "id": "6efcdc61028c8edd1c787b3439fae71b77a17357"}, {"text": "r175:6c0ce52b229a (None)", "id": "6c0ce52b229aa978889e91b38777f800e85f330b"}, {"text": "r165:09788a0b8a54 (None)", "id": "09788a0b8a5455e9678c3959214246574e546d4f"}, {"text": "r163:0164ee729def (None)", "id": "0164ee729def0a253d6dcb594b5ee2a52fef4748"}, {"text": "r140:33fa32233551 (None)", "id": "33fa3223355104431402a888fa77a4e9956feb3e"}, {"text": "r126:fa014c12c26d (None)", "id": "fa014c12c26d10ba682fadb78f2a11c24c8118e1"}, {"text": "r111:e686b958768e (None)", "id": "e686b958768ee96af8029fe19c6050b1a8dd3b2b"}, {"text": "r109:ab5721ca0a08 (None)", "id": "ab5721ca0a081f26bf43d9051e615af2cc99952f"}, {"text": "r108:c877b68d18e7 (None)", "id": "c877b68d18e792a66b7f4c529ea02c8f80801542"}, {"text": "r107:4313566d2e41 (None)", "id": "4313566d2e417cb382948f8d9d7c765330356054"}, {"text": "r104:6c2303a79367 (None)", "id": "6c2303a793671e807d1cfc70134c9ca0767d98c2"}, {"text": "r102:54386793436c (None)", "id": "54386793436c938cff89326944d4c2702340037d"}, {"text": "r101:54000345d2e7 (None)", "id": "54000345d2e78b03a99d561399e8e548de3f3203"}, {"text": "r99:1c6b3677b37e (None)", "id": "1c6b3677b37ea064cb4b51714d8f7498f93f4b2b"}, {"text": "r93:2d03ca750a44 (None)", "id": "2d03ca750a44440fb5ea8b751176d1f36f8e8f46"}, {"text": "r92:2a08b128c206 (None)", "id": "2a08b128c206db48c2f0b8f70df060e6db0ae4f8"}, {"text": "r91:30c26513ff1e (None)", "id": "30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b"}, {"text": "r82:ac71e9503c2c (None)", "id": "ac71e9503c2ca95542839af0ce7b64011b72ea7c"}, {"text": "r81:12669288fd13 (None)", "id": "12669288fd13adba2a9b7dd5b870cc23ffab92d2"}, {"text": "r76:5a0c84f3e6fe (None)", "id": "5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382"}, {"text": "r73:12f2f5e2b38e (None)", "id": "12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5"}, {"text": "r61:5eab1222a7cd (None)", "id": "5eab1222a7cd4bfcbabc218ca6d04276d4e27378"}, {"text": "r60:f50f42baeed5 (None)", "id": "f50f42baeed5af6518ef4b0cb2f1423f3851a941"}, {"text": "r59:d7e390a45f6a (None)", "id": "d7e390a45f6aa96f04f5e7f583ad4f867431aa25"}, {"text": "r58:f15c21f97864 (None)", "id": "f15c21f97864b4f071cddfbf2750ec2e23859414"}, {"text": "r57:e906ef056cf5 (None)", "id": "e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade"}, {"text": "r56:ea2b108b48aa (None)", "id": "ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b"}, {"text": "r50:84dec09632a4 (None)", "id": "84dec09632a4458f79f50ddbbd155506c460b4f9"}, {"text": "r48:0115510b70c7 (None)", "id": "0115510b70c7229dbc5dc49036b32e7d91d23acd"}, {"text": "r46:2a13f185e452 (None)", "id": "2a13f185e4525f9d4b59882791a2d397b90d5ddc"}, {"text": "r30:3bf1c5868e57 (None)", "id": "3bf1c5868e570e39569d094f922d33ced2fa3b2b"}, {"text": "r26:b8d040125747 (None)", "id": "b8d04012574729d2c29886e53b1a43ef16dd00a1"}, {"text": "r24:6970b057cffe (None)", "id": "6970b057cffe4aab0a792aa634c89f4bebf01441"}, {"text": "r8:dd80b0f6cf50 (None)", "id": "dd80b0f6cf5052f17cc738c2951c4f2070200d7f"}, {"text": "r7:ff7ca51e58c5 (None)", "id": "ff7ca51e58c505fec0dd2491de52c622bb7a806b"}]}, {"text": "Branches", "children": [{"text": "master", "id": "5f2c6ee195929b0be80749243c18121c9864a3b3"}]}, {"text": "Tags", "children": [{"text": "v0.2.2", "id": "137fea89f304a42321d40488091ee2ed419a3686"}, {"text": "v0.2.1", "id": "5051d0fa344d4408a2659d9a0348eb2d41868ecf"}, {"text": "v0.2.0", "id": "599ba911aa24d2981225f3966eb659dfae9e9f30"}, {"text": "v0.1.9", "id": "341d28f0eec5ddf0b6b77871e13c2bbd6bec685c"}, {"text": "v0.1.8", "id": "74ebce002c088b8a5ecf40073db09375515ecd68"}, {"text": "v0.1.7", "id": "4d78bf73b5c22c82b68f902f138f7881b4fffa2c"}, {"text": "v0.1.6", "id": "0205cb3f44223fb3099d12a77a69c81b798772d9"}, {"text": "v0.1.5", "id": "6c0ce52b229aa978889e91b38777f800e85f330b"}, {"text": "v0.1.4", "id": "7d735150934cd7645ac3051903add952390324a5"}, {"text": "v0.1.3", "id": "5a3a8fb005554692b16e21dee62bf02667d8dc3e"}, {"text": "v0.1.2", "id": "0ba5f8a4660034ff25c0cac2a5baabf5d2791d63"}, {"text": "v0.1.11", "id": "c60f01b77c42dce653d6b1d3b04689862c261929"}, {"text": "v0.1.10", "id": "10cddef6b794696066fb346434014f0a56810218"}, {"text": "v0.1.1", "id": "e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0"}]}], "more": false}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/fixtures/hg_node_history_response.json	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,1 @@
+{"results": [{"text": "Changesets", "children": [{"text": "r648:dbec37a0d5ca (default)", "id": "dbec37a0d5cab8ff39af4cfc4a4cd3996e4acfc6"}, {"text": "r639:1d20ed9eda94 (default)", "id": "1d20ed9eda9482d46ff0a6af5812550218b3ff15"}, {"text": "r547:0173395e8227 (default)", "id": "0173395e822797f098799ed95c1a81b6a547a9ad"}, {"text": "r546:afbb45ade933 (default)", "id": "afbb45ade933a8182f1d8ec5d4d1bb2de2572043"}, {"text": "r502:6f093e30cac3 (default)", "id": "6f093e30cac34e6b4b11275a9f22f80c5d7ad1f7"}, {"text": "r476:c7e2212dd2ae (default)", "id": "c7e2212dd2ae975d1d06534a3d7e317165c06960"}, {"text": "r472:45477506df79 (default)", "id": "45477506df79f701bf69419aac3e1f0fed3c5bcf"}, {"text": "r469:5fc76cb25d11 (default)", "id": "5fc76cb25d11e07c60de040f78b8cd265ff10d53"}, {"text": "r468:b073433cf899 (default)", "id": "b073433cf8994969ee5cd7cce84cbe587bb880b2"}, {"text": "r467:7a74dbfcacd1 (default)", "id": "7a74dbfcacd1dbcb58bb9c860b2f29fbb22c4c96"}, {"text": "r465:71ee52cc4d62 (default)", "id": "71ee52cc4d629096bdbee036325975dac2af4501"}, {"text": "r452:a5b217d26c5f (default)", "id": "a5b217d26c5f111e72bae4de672b084ee0fbf75c"}, {"text": "r450:47aedd538bf6 (default)", "id": "47aedd538bf616eedcb0e7d630ea476df0e159c7"}, {"text": "r432:8e4915fa32d7 (default)", "id": "8e4915fa32d727dcbf09746f637a5f82e539511e"}, {"text": "r356:25213a5fbb04 (default)", "id": "25213a5fbb048dff8ba65d21e466a835536e5b70"}, {"text": "r351:23debcedddc1 (default)", "id": "23debcedddc1c23c14be33e713e7786d4a9de471"}, {"text": "r342:61e25b2a90a1 (default)", "id": "61e25b2a90a19e7fffd75dea1e4c7e20df526bbe"}, {"text": "r318:fb95b340e0d0 (webvcs)", "id": "fb95b340e0d03fa51f33c56c991c08077c99303e"}, {"text": "r303:bda35e0e564f (default)", "id": "bda35e0e564fbbc5cd26fe0a37fb647a254c99fe"}, {"text": "r302:97ff74896d7d (default)", "id": "97ff74896d7dbf3115a337a421d44b55154acc89"}, {"text": "r293:cec3473c3fdb (default)", "id": "cec3473c3fdb9599c98067182a075b49bde570f9"}, {"text": "r289:0e86c43eef86 (default)", "id": "0e86c43eef866a013a587666a877c879899599bb"}, {"text": "r288:91a27c312808 (default)", "id": "91a27c312808100cf20a602f78befbbff9d89bfd"}, {"text": "r287:400e36a1670a (default)", "id": "400e36a1670a57d11e3edcb5b07bf82c30006d0b"}, {"text": "r261:014fb17dfc95 (default)", "id": "014fb17dfc95b0995e838c565376bf9a993e230a"}, {"text": "r260:cca7aebbc4d6 (default)", "id": "cca7aebbc4d6125798446b11e69dc8847834a982"}, {"text": "r258:14cdb2957c01 (workdir)", "id": "14cdb2957c011a5feba36f50d960d9832ba0f0c1"}, {"text": "r245:34df20118ed7 (default)", "id": "34df20118ed74b5987d22a579e8a60e903da5bf8"}, {"text": "r233:0375d9042a64 (workdir)", "id": "0375d9042a64a1ac1641528f0f0668f9a339e86d"}, {"text": "r222:94aa45fc1806 (workdir)", "id": "94aa45fc1806c04d4ba640933edf682c22478453"}, {"text": "r188:7ed99bc73881 (default)", "id": "7ed99bc738818879941e3ce20243f8856a7cfc84"}, {"text": "r184:1e85975528bc (default)", "id": "1e85975528bcebe853732a9e5fb8dbf4461f6bb2"}, {"text": "r183:ed30beddde7b (default)", "id": "ed30beddde7bbddb26042625be19bcd11576c1dd"}, {"text": "r177:a6664e18181c (default)", "id": "a6664e18181c6fc81b751a8d01474e7e1a3fe7fc"}, {"text": "r167:8911406ad776 (default)", "id": "8911406ad776fdd3d0b9932a2e89677e57405a48"}, {"text": "r165:aa957ed78c35 (default)", "id": "aa957ed78c35a1541f508d2ec90e501b0a9e3167"}, {"text": "r140:48e11b73e94c (default)", "id": "48e11b73e94c0db33e736eaeea692f990cb0b5f1"}, {"text": "r126:adf3cbf48329 (default)", "id": "adf3cbf483298563b968a6c673cd5bde5f7d5eea"}, {"text": "r113:6249fd0fb2cf (git)", "id": "6249fd0fb2cfb1411e764129f598e2cf0de79a6f"}, {"text": "r109:75feb4c33e81 (default)", "id": "75feb4c33e81186c87eac740cee2447330288412"}, {"text": "r108:9a4dc232ecdc (default)", "id": "9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d"}, {"text": "r107:595cce4efa21 (default)", "id": "595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d"}, {"text": "r104:4a8bd421fbc2 (default)", "id": "4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da"}, {"text": "r102:57be63fc8f85 (default)", "id": "57be63fc8f85e65a0106a53187f7316f8c487ffa"}, {"text": "r101:5530bd87f7e2 (git)", "id": "5530bd87f7e2e124a64d07cb2654c997682128be"}, {"text": "r99:e516008b1c93 (default)", "id": "e516008b1c93f142263dc4b7961787cbad654ce1"}, {"text": "r93:41f43fc74b8b (default)", "id": "41f43fc74b8b285984554532eb105ac3be5c434f"}, {"text": "r92:cc66b61b8455 (default)", "id": "cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e"}, {"text": "r91:73ab5b616b32 (default)", "id": "73ab5b616b3271b0518682fb4988ce421de8099f"}, {"text": "r82:e0da75f308c0 (default)", "id": "e0da75f308c0f18f98e9ce6257626009fdda2b39"}, {"text": "r81:fb2e41e0f081 (default)", "id": "fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611"}, {"text": "r76:602ae2f5e7ad (default)", "id": "602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028"}, {"text": "r73:a066b25d5df7 (default)", "id": "a066b25d5df7016b45a41b7e2a78c33b57adc235"}, {"text": "r61:637a933c9059 (web)", "id": "637a933c905958ce5151f154147c25c1c7b68832"}, {"text": "r60:0c21004effeb (web)", "id": "0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc"}, {"text": "r59:a1f39c56d3f1 (web)", "id": "a1f39c56d3f1d52d5fb5920370a2a2716cd9a444"}, {"text": "r58:97d32df05c71 (web)", "id": "97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f"}, {"text": "r57:08eaf1451771 (web)", "id": "08eaf14517718dccea4b67755a93368341aca919"}, {"text": "r56:22f71ad26526 (web)", "id": "22f71ad265265a53238359c883aa976e725aa07d"}, {"text": "r49:97501f02b7b4 (web)", "id": "97501f02b7b4330924b647755663a2d90a5e638d"}, {"text": "r47:86ede6754f2b (web)", "id": "86ede6754f2b27309452bb11f997386ae01d0e5a"}, {"text": "r45:014c40c0203c (web)", "id": "014c40c0203c423dc19ecf94644f7cac9d4cdce0"}, {"text": "r30:ee87846a61c1 (default)", "id": "ee87846a61c12153b51543bf860e1026c6d3dcba"}, {"text": "r26:9bb326a04ae5 (default)", "id": "9bb326a04ae5d98d437dece54be04f830cf1edd9"}, {"text": "r24:536c1a194283 (default)", "id": "536c1a19428381cfea92ac44985304f6a8049569"}, {"text": "r8:dc5d2c0661b6 (default)", "id": "dc5d2c0661b61928834a785d3e64a3f80d3aad9c"}, {"text": "r7:3803844fdbd3 (default)", "id": "3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"}]}, {"text": "Branches", "children": [{"text": "default", "id": "96507bd11ecc815ebc6270fdf6db110928c09c1e"}, {"text": "stable", "id": "4f7e2131323e0749a740c0a56ab68ae9269c562a"}]}, {"text": "Tags", "children": [{"text": "v0.2.0", "id": "2c96c02def9a7c997f33047761a53943e6254396"}, {"text": "v0.1.9", "id": "8680b1d1cee3aa3c1ab3734b76ee164bbedbc5c9"}, {"text": "v0.1.8", "id": "ecb25ba9c96faf1e65a0bc3fd914918420a2f116"}, {"text": "v0.1.7", "id": "f67633a2894edaf28513706d558205fa93df9209"}, {"text": "v0.1.6", "id": "02b38c0eb6f982174750c0e309ff9faddc0c7e12"}, {"text": "v0.1.5", "id": "a6664e18181c6fc81b751a8d01474e7e1a3fe7fc"}, {"text": "v0.1.4", "id": "fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200"}, {"text": "v0.1.3", "id": "17544fbfcd33ffb439e2b728b5d526b1ef30bfcf"}, {"text": "v0.1.2", "id": "a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720"}, {"text": "v0.1.11", "id": "fef5bfe1dc17611d5fb59a7f6f95c55c3606f933"}, {"text": "v0.1.10", "id": "92831aebf2f8dd4879e897024b89d09af214df1c"}, {"text": "v0.1.1", "id": "eb3a60fc964309c1a318b8dfe26aa2d1586c85ae"}, {"text": "tip", "id": "96507bd11ecc815ebc6270fdf6db110928c09c1e"}]}], "more": false}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/functional/test_admin_auth_settings.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,113 @@
+from rhodecode.tests import *
+from rhodecode.model.db import RhodeCodeSetting
+
+
+class TestAuthSettingsController(TestController):
+    def _enable_plugins(self, plugins_list):
+        test_url = url(controller='admin/auth_settings',
+                       action='auth_settings')
+        params={'auth_plugins': plugins_list,}
+
+        for plugin in plugins_list.split(','):
+            enable = plugin.partition('rhodecode.lib.auth_modules.')[-1]
+            params.update({'%s_enabled' % enable: True})
+        response = self.app.post(url=test_url, params=params)
+        return params
+        #self.checkSessionFlash(response, 'Auth settings updated successfully')
+
+    def test_index(self):
+        self.log_user()
+        response = self.app.get(url(controller='admin/auth_settings',
+                                    action='index'))
+        response.mustcontain('Authentication Plugins')
+
+    def test_ldap_save_settings(self):
+        self.log_user()
+        if ldap_lib_installed:
+            raise SkipTest('skipping due to missing ldap lib')
+
+        params = self._enable_plugins('rhodecode.lib.auth_modules.auth_rhodecode,rhodecode.lib.auth_modules.auth_ldap')
+        params.update({'auth_ldap_host': u'dc.example.com',
+                       'auth_ldap_port': '999',
+                       'auth_ldap_tls_kind': 'PLAIN',
+                       'auth_ldap_tls_reqcert': 'NEVER',
+                       'auth_ldap_dn_user': 'test_user',
+                       'auth_ldap_dn_pass': 'test_pass',
+                       'auth_ldap_base_dn': 'test_base_dn',
+                       'auth_ldap_filter': 'test_filter',
+                       'auth_ldap_search_scope': 'BASE',
+                       'auth_ldap_attr_login': 'test_attr_login',
+                       'auth_ldap_attr_firstname': 'ima',
+                       'auth_ldap_attr_lastname': 'tester',
+                       'auth_ldap_attr_email': 'test@example.com'})
+
+        test_url = url(controller='admin/auth_settings',
+                       action='auth_settings')
+
+        response = self.app.post(url=test_url, params=params)
+        self.checkSessionFlash(response, 'Auth settings updated successfully')
+
+        new_settings = RhodeCodeSetting.get_auth_settings()
+        self.assertEqual(new_settings['auth_ldap_host'], u'dc.example.com',
+                         'fail db write compare')
+
+    def test_ldap_error_form_wrong_port_number(self):
+        self.log_user()
+        if ldap_lib_installed:
+            raise SkipTest('skipping due to missing ldap lib')
+
+        params = self._enable_plugins('rhodecode.lib.auth_modules.auth_rhodecode,rhodecode.lib.auth_modules.auth_ldap')
+        params.update({'auth_ldap_host': '',
+                       'auth_ldap_port': 'i-should-be-number',  # bad port num
+                       'auth_ldap_tls_kind': 'PLAIN',
+                       'auth_ldap_tls_reqcert': 'NEVER',
+                       'auth_ldap_dn_user': '',
+                       'auth_ldap_dn_pass': '',
+                       'auth_ldap_base_dn': '',
+                       'auth_ldap_filter': '',
+                       'auth_ldap_search_scope': 'BASE',
+                       'auth_ldap_attr_login': '',
+                       'auth_ldap_attr_firstname': '',
+                       'auth_ldap_attr_lastname': '',
+                       'auth_ldap_attr_email': ''})
+        test_url = url(controller='admin/auth_settings',
+                       action='auth_settings')
+
+        response = self.app.post(url=test_url, params=params)
+
+        response.mustcontain("""<span class="error-message">"""
+                             """Please enter a number</span>""")
+
+    def test_ldap_error_form(self):
+        self.log_user()
+        if ldap_lib_installed:
+            raise SkipTest('skipping due to missing ldap lib')
+
+        params = self._enable_plugins('rhodecode.lib.auth_modules.auth_rhodecode,rhodecode.lib.auth_modules.auth_ldap')
+        params.update({'auth_ldap_host': 'Host',
+                       'auth_ldap_port': '123',
+                       'auth_ldap_tls_kind': 'PLAIN',
+                       'auth_ldap_tls_reqcert': 'NEVER',
+                       'auth_ldap_dn_user': '',
+                       'auth_ldap_dn_pass': '',
+                       'auth_ldap_base_dn': '',
+                       'auth_ldap_filter': '',
+                       'auth_ldap_search_scope': 'BASE',
+                       'auth_ldap_attr_login': '',  # <----- missing required input
+                       'auth_ldap_attr_firstname': '',
+                       'auth_ldap_attr_lastname': '',
+                       'auth_ldap_attr_email': ''})
+
+        test_url = url(controller='admin/auth_settings',
+                       action='auth_settings')
+
+        response = self.app.post(url=test_url, params=params)
+
+        response.mustcontain("""<span class="error-message">The LDAP Login"""
+                             """ attribute of the CN must be specified""")
+
+    def test_ldap_login(self):
+        pass
+
+    def test_ldap_login_incorrect(self):
+        pass
--- a/rhodecode/tests/functional/test_admin_defaults.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_admin_defaults.py	Wed Jul 02 19:03:13 2014 -0400
@@ -24,7 +24,7 @@
     def test_new_as_xml(self):
         response = self.app.get(url('formatted_new_default', format='xml'))
 
-    def test_update(self):
+    def test_update_params_true_hg(self):
         self.log_user()
         params = {
             'default_repo_enable_locking': True,
@@ -35,9 +35,12 @@
         }
         response = self.app.put(url('default', id='default'), params=params)
         self.checkSessionFlash(response, 'Default settings updated successfully')
+
         defs = RhodeCodeSetting.get_default_repo_settings()
         self.assertEqual(params, defs)
 
+    def test_update_params_false_git(self):
+        self.log_user()
         params = {
             'default_repo_enable_locking': False,
             'default_repo_enable_downloads': False,
--- a/rhodecode/tests/functional/test_admin_gists.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_admin_gists.py	Wed Jul 02 19:03:13 2014 -0400
@@ -39,12 +39,12 @@
         g4 = _create_gist('gist4', gist_type='private').gist_access_id
         response = self.app.get(url('gists'))
         # Test response...
-        response.mustcontain('gist:%s' % g1)
-        response.mustcontain('gist:%s' % g2)
+        response.mustcontain('gist: %s' % g1)
+        response.mustcontain('gist: %s' % g2)
         response.mustcontain('Expires: in 23 hours')  # we don't care about the end
-        response.mustcontain('gist:%s' % g3)
+        response.mustcontain('gist: %s' % g3)
         response.mustcontain('gist3-desc')
-        response.mustcontain(no=['gist:%s' % g4])
+        response.mustcontain(no=['gist: %s' % g4])
 
     def test_index_private_gists(self):
         self.log_user()
@@ -53,7 +53,7 @@
         # Test response...
 
         #and privates
-        response.mustcontain('gist:%s' % gist.gist_access_id)
+        response.mustcontain('gist: %s' % gist.gist_access_id)
 
     def test_create_missing_description(self):
         self.log_user()
@@ -73,7 +73,7 @@
         response = response.follow()
         response.mustcontain('added file: foo')
         response.mustcontain('gist test')
-        response.mustcontain('<div class="ui-btn green badge">Public gist</div>')
+        response.mustcontain('<div class="btn btn-mini btn-success disabled">Public Gist</div>')
 
     def test_create_with_path_with_dirs(self):
         self.log_user()
@@ -105,7 +105,7 @@
         response = response.follow()
         response.mustcontain('added file: private-foo<')
         response.mustcontain('private gist test')
-        response.mustcontain('<div class="ui-btn yellow badge">Private gist</div>')
+        response.mustcontain('<div class="btn btn-mini btn-warning disabled">Private Gist</div>')
 
     def test_create_with_description(self):
         self.log_user()
@@ -120,7 +120,7 @@
         response.mustcontain('added file: foo-desc')
         response.mustcontain('gist test')
         response.mustcontain('gist-desc')
-        response.mustcontain('<div class="ui-btn green badge">Public gist</div>')
+        response.mustcontain('<div class="btn btn-mini btn-success disabled">Public Gist</div>')
 
     def test_new(self):
         self.log_user()
@@ -153,7 +153,7 @@
         response.mustcontain('added file: gist-show-me<')
         response.mustcontain('test_admin (RhodeCode Admin) - created')
         response.mustcontain('gist-desc')
-        response.mustcontain('<div class="ui-btn green badge">Public gist</div>')
+        response.mustcontain('<div class="btn btn-mini btn-success disabled">Public Gist</div>')
 
     def test_show_as_raw(self):
         gist = _create_gist('gist-show-me', content='GIST CONTENT')
@@ -169,5 +169,4 @@
         self.assertEqual(response.body, 'GIST BODY')
 
     def test_edit(self):
-        self.skipTest('not implemented')
         response = self.app.get(url('edit_gist', gist_id=1))
--- a/rhodecode/tests/functional/test_admin_ldap_settings.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-from rhodecode.tests import *
-from rhodecode.model.db import RhodeCodeSetting
-
-class TestLdapSettingsController(TestController):
-
-    def test_index(self):
-        self.log_user()
-        response = self.app.get(url(controller='admin/ldap_settings',
-                                    action='index'))
-        response.mustcontain('LDAP administration')
-
-    def test_ldap_save_settings(self):
-        self.log_user()
-        if ldap_lib_installed:
-            raise SkipTest('skipping due to missing ldap lib')
-
-        test_url = url(controller='admin/ldap_settings',
-                       action='ldap_settings')
-
-        response = self.app.post(url=test_url,
-            params={'ldap_host': u'dc.example.com',
-                    'ldap_port': '999',
-                    'ldap_tls_kind': 'PLAIN',
-                    'ldap_tls_reqcert': 'NEVER',
-                    'ldap_dn_user': 'test_user',
-                    'ldap_dn_pass': 'test_pass',
-                    'ldap_base_dn': 'test_base_dn',
-                    'ldap_filter': 'test_filter',
-                    'ldap_search_scope': 'BASE',
-                    'ldap_attr_login': 'test_attr_login',
-                    'ldap_attr_firstname': 'ima',
-                    'ldap_attr_lastname': 'tester',
-                    'ldap_attr_email': 'test@example.com' })
-
-        new_settings = RhodeCodeSetting.get_ldap_settings()
-        self.assertEqual(new_settings['ldap_host'], u'dc.example.com',
-                         'fail db write compare')
-
-        self.checkSessionFlash(response,
-                               'LDAP settings updated successfully')
-
-    def test_ldap_error_form_wrong_port_number(self):
-        self.log_user()
-        if ldap_lib_installed:
-            raise SkipTest('skipping due to missing ldap lib')
-
-        test_url = url(controller='admin/ldap_settings',
-                       action='ldap_settings')
-
-        response = self.app.post(url=test_url,
-            params={'ldap_host': '',
-                    'ldap_port': 'i-should-be-number',  # bad port num
-                    'ldap_tls_kind': 'PLAIN',
-                    'ldap_tls_reqcert': 'NEVER',
-                    'ldap_dn_user': '',
-                    'ldap_dn_pass': '',
-                    'ldap_base_dn': '',
-                    'ldap_filter': '',
-                    'ldap_search_scope': 'BASE',
-                    'ldap_attr_login': '',
-                    'ldap_attr_firstname': '',
-                    'ldap_attr_lastname': '',
-                    'ldap_attr_email': ''})
-
-        response.mustcontain("""<span class="error-message">"""
-                             """Please enter a number</span><br />""")
-
-    def test_ldap_error_form(self):
-        self.log_user()
-        if ldap_lib_installed:
-            raise SkipTest('skipping due to missing ldap lib')
-
-        test_url = url(controller='admin/ldap_settings',
-                       action='ldap_settings')
-
-        response = self.app.post(url=test_url,
-            params={'ldap_host': 'Host',
-                    'ldap_port': '123',
-                    'ldap_tls_kind': 'PLAIN',
-                    'ldap_tls_reqcert': 'NEVER',
-                    'ldap_dn_user': '',
-                    'ldap_dn_pass': '',
-                    'ldap_base_dn': '',
-                    'ldap_filter': '',
-                    'ldap_search_scope': 'BASE',
-                    'ldap_attr_login': '',  # <----- missing required input
-                    'ldap_attr_firstname': '',
-                    'ldap_attr_lastname': '',
-                    'ldap_attr_email': ''})
-
-        response.mustcontain("""<span class="error-message">The LDAP Login"""
-                             """ attribute of the CN must be specified""")
-
-    def test_ldap_login(self):
-        pass
-
-    def test_ldap_login_incorrect(self):
-        pass
--- a/rhodecode/tests/functional/test_admin_notifications.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_admin_notifications.py	Wed Jul 02 19:03:13 2014 -0400
@@ -18,39 +18,19 @@
         self.log_user()
 
         u1 = UserModel().create_or_update(username='u1', password='qweqwe',
-                                               email='u1@rhodecode.org',
-                                               firstname='u1', lastname='u1')
+                                          email='u1@rhodecode.org',
+                                          firstname='u1', lastname='u1')
         u1 = u1.user_id
 
         response = self.app.get(url('notifications'))
         response.mustcontain('<div class="table">No notifications here yet</div>')
 
         cur_user = self._get_logged_user()
-
-        NotificationModel().create(created_by=u1, subject=u'test_notification_1',
-                                   body=u'notification_1',
-                                   recipients=[cur_user])
+        notif = NotificationModel().create(created_by=u1, subject=u'test_notification_1',
+                                           body=u'notification_1', recipients=[cur_user])
         Session().commit()
         response = self.app.get(url('notifications'))
-        response.mustcontain(u'test_notification_1')
-
-#    def test_index_as_xml(self):
-#        response = self.app.get(url('formatted_notifications', format='xml'))
-#
-#    def test_create(self):
-#        response = self.app.post(url('notifications'))
-#
-#    def test_new(self):
-#        response = self.app.get(url('new_notification'))
-#
-#    def test_new_as_xml(self):
-#        response = self.app.get(url('formatted_new_notification', format='xml'))
-#
-#    def test_update(self):
-#        response = self.app.put(url('notification', notification_id=1))
-#
-#    def test_update_browser_fakeout(self):
-#        response = self.app.post(url('notification', notification_id=1), params=dict(_method='put'))
+        response.mustcontain('id="notification_%s"' % notif.notification_id)
 
     def test_delete(self):
         self.log_user()
@@ -91,16 +71,21 @@
         self.log_user()
         cur_user = self._get_logged_user()
         u1 = UserModel().create_or_update(username='u1', password='qweqwe',
-                                               email='u1@rhodecode.org',
-                                               firstname='u1', lastname='u1')
+                                          email='u1@rhodecode.org',
+                                          firstname='u1', lastname='u1')
         u2 = UserModel().create_or_update(username='u2', password='qweqwe',
-                                               email='u2@rhodecode.org',
-                                               firstname='u2', lastname='u2')
+                                          email='u2@rhodecode.org',
+                                          firstname='u2', lastname='u2')
 
+        subject = u'test'
+        notif_body = u'hi there'
         notification = NotificationModel().create(created_by=cur_user,
-                                                  subject=u'test',
-                                                  body=u'hi there',
+                                                  subject=subject,
+                                                  body=notif_body,
                                                   recipients=[cur_user, u1, u2])
 
         response = self.app.get(url('notification',
                                     notification_id=notification.notification_id))
+
+        response.mustcontain(subject)
+        response.mustcontain(notif_body)
--- a/rhodecode/tests/functional/test_admin_permissions.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_admin_permissions.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,43 +1,45 @@
+from rhodecode.model.db import User, UserIpMap
 from rhodecode.tests import *
 
 class TestAdminPermissionsController(TestController):
 
     def test_index(self):
-        response = self.app.get(url('permissions'))
+        self.log_user()
+        response = self.app.get(url('admin_permissions'))
         # Test response...
 
-    def test_index_as_xml(self):
-        response = self.app.get(url('formatted_permissions', format='xml'))
-
-    def test_create(self):
-        response = self.app.post(url('permissions'))
-
-    def test_new(self):
-        response = self.app.get(url('new_permission'))
+    def test_index_ips(self):
+        self.log_user()
+        response = self.app.get(url('admin_permissions_ips'))
+        # Test response...
+        response.mustcontain('All IP addresses are allowed')
 
-    def test_new_as_xml(self):
-        response = self.app.get(url('formatted_new_permission', format='xml'))
+    def test_add_ips(self):
+        self.log_user()
+        default_user_id = User.get_default_user().user_id
+        response = self.app.put(url('edit_user_ips', id=default_user_id),
+                                 params=dict(new_ip='127.0.0.0/24'))
 
-    def test_update(self):
-        response = self.app.put(url('permission', id=1))
-
-    def test_update_browser_fakeout(self):
-        response = self.app.post(url('permission', id=1), params=dict(_method='put'))
+        response = self.app.get(url('admin_permissions_ips'))
+        response.mustcontain('127.0.0.0/24')
+        response.mustcontain('127.0.0.0 - 127.0.0.255')
 
-    def test_delete(self):
-        response = self.app.delete(url('permission', id=1))
+        ## delete
+        default_user_id = User.get_default_user().user_id
+        del_ip_id = UserIpMap.query().filter(UserIpMap.user_id ==
+                                             default_user_id).first().ip_id
 
-    def test_delete_browser_fakeout(self):
-        response = self.app.post(url('permission', id=1), params=dict(_method='delete'))
-
-    def test_show(self):
-        response = self.app.get(url('permission', id=1))
+        response = self.app.post(url('edit_user_ips', id=default_user_id),
+                                 params=dict(_method='delete',
+                                             del_ip_id=del_ip_id))
 
-    def test_show_as_xml(self):
-        response = self.app.get(url('formatted_permission', id=1, format='xml'))
+        response = self.app.get(url('admin_permissions_ips'))
+        response.mustcontain('All IP addresses are allowed')
+        response.mustcontain(no=['127.0.0.0/24'])
+        response.mustcontain(no=['127.0.0.0 - 127.0.0.255'])
 
-    def test_edit(self):
-        response = self.app.get(url('edit_permission', id=1))
 
-    def test_edit_as_xml(self):
-        response = self.app.get(url('formatted_edit_permission', id=1, format='xml'))
+    def test_index_overview(self):
+        self.log_user()
+        response = self.app.get(url('admin_permissions_perms'))
+        # Test response...
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/functional/test_admin_repo_groups.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,4 @@
+from rhodecode.tests import *
+
+class TestRepoGroupsController(TestController):
+    pass
--- a/rhodecode/tests/functional/test_admin_repos.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_admin_repos.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,16 +1,19 @@
 # -*- coding: utf-8 -*-
 
 import os
+import mock
 import urllib
 
 from rhodecode.lib import vcs
 from rhodecode.model.db import Repository, RepoGroup, UserRepoToPerm, User,\
     Permission
+from rhodecode.model.user import UserModel
 from rhodecode.tests import *
-from rhodecode.model.repos_group import ReposGroupModel
+from rhodecode.model.repo_group import RepoGroupModel
 from rhodecode.model.repo import RepoModel
+from rhodecode.model.scm import ScmModel
 from rhodecode.model.meta import Session
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixture import Fixture, error_function
 
 fixture = Fixture()
 
@@ -24,87 +27,109 @@
     return perm
 
 
-class TestAdminReposController(TestController):
+class _BaseTest(TestController):
+    """
+    Write all tests here
+    """
+    REPO = None
+    REPO_TYPE = None
+    NEW_REPO = None
+    OTHER_TYPE_REPO = None
+    OTHER_TYPE = None
+
+    @classmethod
+    def setup_class(cls):
+        pass
+
+    @classmethod
+    def teardown_class(cls):
+        pass
 
     def test_index(self):
         self.log_user()
         response = self.app.get(url('repos'))
-        # Test response...
 
-    def test_index_as_xml(self):
-        response = self.app.get(url('formatted_repos', format='xml'))
-
-    def test_create_hg(self):
+    def test_create(self):
         self.log_user()
-        repo_name = NEW_HG_REPO
+        repo_name = self.NEW_REPO
         description = 'description for newly created repo'
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
                                                 repo_description=description))
+        ## run the check page that triggers the flash message
+        response = self.app.get(url('repo_check_home', repo_name=repo_name))
+        self.assertEqual(response.json, {u'result': True})
         self.checkSessionFlash(response,
                                'Created repository <a href="/%s">%s</a>'
                                % (repo_name, repo_name))
 
-        #test if the repo was created in the database
+        # test if the repo was created in the database
         new_repo = Session().query(Repository)\
             .filter(Repository.repo_name == repo_name).one()
 
         self.assertEqual(new_repo.repo_name, repo_name)
         self.assertEqual(new_repo.description, description)
 
-        #test if repository is visible in the list ?
-        response = response.follow()
+        # test if the repository is visible in the list ?
+        response = self.app.get(url('summary_home', repo_name=repo_name))
+        response.mustcontain(repo_name)
+        response.mustcontain(self.REPO_TYPE)
 
-        response.mustcontain(repo_name)
-
-        #test if repository was created on filesystem
+        # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
         except Exception:
             self.fail('no repo %s in filesystem' % repo_name)
 
-    def test_create_hg_non_ascii(self):
+        RepoModel().delete(repo_name)
+        Session().commit()
+
+    def test_create_non_ascii(self):
         self.log_user()
         non_ascii = "ฤ…ฤ™ล‚"
-        repo_name = "%s%s" % (NEW_HG_REPO, non_ascii)
+        repo_name = "%s%s" % (self.NEW_REPO, non_ascii)
         repo_name_unicode = repo_name.decode('utf8')
         description = 'description for newly created repo' + non_ascii
         description_unicode = description.decode('utf8')
-        private = False
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
                                                 repo_description=description))
+        ## run the check page that triggers the flash message
+        response = self.app.get(url('repo_check_home', repo_name=repo_name))
+        self.assertEqual(response.json, {u'result': True})
         self.checkSessionFlash(response,
                                u'Created repository <a href="/%s">%s</a>'
                                % (urllib.quote(repo_name), repo_name_unicode))
-        #test if the repo was created in the database
+        # test if the repo was created in the database
         new_repo = Session().query(Repository)\
             .filter(Repository.repo_name == repo_name_unicode).one()
 
         self.assertEqual(new_repo.repo_name, repo_name_unicode)
         self.assertEqual(new_repo.description, description_unicode)
 
-        #test if repository is visible in the list ?
-        response = response.follow()
+        # test if the repository is visible in the list ?
+        response = self.app.get(url('summary_home', repo_name=repo_name))
+        response.mustcontain(repo_name)
+        response.mustcontain(self.REPO_TYPE)
 
-        response.mustcontain(repo_name)
-
-        #test if repository was created on filesystem
+        # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
         except Exception:
             self.fail('no repo %s in filesystem' % repo_name)
 
-    def test_create_hg_in_group(self):
+    def test_create_in_group(self):
         self.log_user()
 
         ## create GROUP
-        group_name = 'sometest'
-        gr = ReposGroupModel().create(group_name=group_name,
-                                      group_description='test',
-                                      owner=TEST_USER_ADMIN_LOGIN)
+        group_name = 'sometest_%s' % self.REPO_TYPE
+        gr = RepoGroupModel().create(group_name=group_name,
+                                     group_description='test',
+                                     owner=TEST_USER_ADMIN_LOGIN)
         Session().commit()
 
         repo_name = 'ingroup'
@@ -113,137 +138,251 @@
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
+                                                repo_description=description,
+                                                repo_group=gr.group_id,))
+        ## run the check page that triggers the flash message
+        response = self.app.get(url('repo_check_home', repo_name=repo_name_full))
+        self.assertEqual(response.json, {u'result': True})
+        self.checkSessionFlash(response,
+                               'Created repository <a href="/%s">%s</a>'
+                               % (repo_name_full, repo_name_full))
+        # test if the repo was created in the database
+        new_repo = Session().query(Repository)\
+            .filter(Repository.repo_name == repo_name_full).one()
+        new_repo_id = new_repo.repo_id
+
+        self.assertEqual(new_repo.repo_name, repo_name_full)
+        self.assertEqual(new_repo.description, description)
+
+        # test if the repository is visible in the list ?
+        response = self.app.get(url('summary_home', repo_name=repo_name_full))
+        response.mustcontain(repo_name_full)
+        response.mustcontain(self.REPO_TYPE)
+
+        inherited_perms = UserRepoToPerm.query()\
+            .filter(UserRepoToPerm.repository_id == new_repo_id).all()
+        self.assertEqual(len(inherited_perms), 1)
+
+        # test if the repository was created on filesystem
+        try:
+            vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
+        except Exception:
+            RepoGroupModel().delete(group_name)
+            Session().commit()
+            self.fail('no repo %s in filesystem' % repo_name)
+
+        RepoModel().delete(repo_name_full)
+        RepoGroupModel().delete(group_name)
+        Session().commit()
+
+    def test_create_in_group_without_needed_permissions(self):
+        usr = self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+        # revoke
+        user_model = UserModel()
+        # disable fork and create on default user
+        user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
+        user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
+        user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
+        user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
+
+        # disable on regular user
+        user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
+        user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
+        user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
+        user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
+        Session().commit()
+
+        ## create GROUP
+        group_name = 'reg_sometest_%s' % self.REPO_TYPE
+        gr = RepoGroupModel().create(group_name=group_name,
+                                     group_description='test',
+                                     owner=TEST_USER_ADMIN_LOGIN)
+        Session().commit()
+
+        group_name_allowed = 'reg_sometest_allowed_%s' % self.REPO_TYPE
+        gr_allowed = RepoGroupModel().create(group_name=group_name_allowed,
+                                     group_description='test',
+                                     owner=TEST_USER_REGULAR_LOGIN)
+        Session().commit()
+
+        repo_name = 'ingroup'
+        repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
+        description = 'description for newly created repo'
+        response = self.app.post(url('repos'),
+                        fixture._get_repo_create_params(repo_private=False,
+                                                repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
                                                 repo_description=description,
                                                 repo_group=gr.group_id,))
 
+        response.mustcontain('Invalid value')
+
+        # user is allowed to create in this group
+        repo_name = 'ingroup'
+        repo_name_full = RepoGroup.url_sep().join([group_name_allowed, repo_name])
+        description = 'description for newly created repo'
+        response = self.app.post(url('repos'),
+                        fixture._get_repo_create_params(repo_private=False,
+                                                repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
+                                                repo_description=description,
+                                                repo_group=gr_allowed.group_id,))
+
+        ## run the check page that triggers the flash message
+        response = self.app.get(url('repo_check_home', repo_name=repo_name_full))
+        self.assertEqual(response.json, {u'result': True})
         self.checkSessionFlash(response,
                                'Created repository <a href="/%s">%s</a>'
-                               % (repo_name_full, repo_name))
-        #test if the repo was created in the database
+                               % (repo_name_full, repo_name_full))
+        # test if the repo was created in the database
         new_repo = Session().query(Repository)\
             .filter(Repository.repo_name == repo_name_full).one()
+        new_repo_id = new_repo.repo_id
 
         self.assertEqual(new_repo.repo_name, repo_name_full)
         self.assertEqual(new_repo.description, description)
 
-        #test if repository is visible in the list ?
-        response = response.follow()
+        # test if the repository is visible in the list ?
+        response = self.app.get(url('summary_home', repo_name=repo_name_full))
+        response.mustcontain(repo_name_full)
+        response.mustcontain(self.REPO_TYPE)
 
-        response.mustcontain(repo_name_full)
+        inherited_perms = UserRepoToPerm.query()\
+            .filter(UserRepoToPerm.repository_id == new_repo_id).all()
+        self.assertEqual(len(inherited_perms), 1)
 
-        #test if repository was created on filesystem
+        # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
         except Exception:
-            ReposGroupModel().delete(group_name)
+            RepoGroupModel().delete(group_name)
             Session().commit()
             self.fail('no repo %s in filesystem' % repo_name)
 
         RepoModel().delete(repo_name_full)
-        ReposGroupModel().delete(group_name)
+        RepoGroupModel().delete(group_name)
+        RepoGroupModel().delete(group_name_allowed)
+        Session().commit()
+
+    def test_create_in_group_inherit_permissions(self):
+        self.log_user()
+
+        ## create GROUP
+        group_name = 'sometest_%s' % self.REPO_TYPE
+        gr = RepoGroupModel().create(group_name=group_name,
+                                     group_description='test',
+                                     owner=TEST_USER_ADMIN_LOGIN)
+        perm = Permission.get_by_key('repository.write')
+        RepoGroupModel().grant_user_permission(gr, TEST_USER_REGULAR_LOGIN, perm)
+
+        ## add repo permissions
         Session().commit()
 
-    def test_create_git(self):
-        self.log_user()
-        repo_name = NEW_GIT_REPO
+        repo_name = 'ingroup_inherited_%s' % self.REPO_TYPE
+        repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
         description = 'description for newly created repo'
-
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
-                                                repo_type='git',
+                                                repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
+                                                repo_description=description,
+                                                repo_group=gr.group_id,
+                                                repo_copy_permissions=True))
+
+        ## run the check page that triggers the flash message
+        response = self.app.get(url('repo_check_home', repo_name=repo_name_full))
+        self.checkSessionFlash(response,
+                               'Created repository <a href="/%s">%s</a>'
+                               % (repo_name_full, repo_name_full))
+        # test if the repo was created in the database
+        new_repo = Session().query(Repository)\
+            .filter(Repository.repo_name == repo_name_full).one()
+        new_repo_id = new_repo.repo_id
+
+        self.assertEqual(new_repo.repo_name, repo_name_full)
+        self.assertEqual(new_repo.description, description)
+
+        # test if the repository is visible in the list ?
+        response = self.app.get(url('summary_home', repo_name=repo_name_full))
+        response.mustcontain(repo_name_full)
+        response.mustcontain(self.REPO_TYPE)
+
+        # test if the repository was created on filesystem
+        try:
+            vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
+        except Exception:
+            RepoGroupModel().delete(group_name)
+            Session().commit()
+            self.fail('no repo %s in filesystem' % repo_name)
+
+        #check if inherited permissiona are applied
+        inherited_perms = UserRepoToPerm.query()\
+            .filter(UserRepoToPerm.repository_id == new_repo_id).all()
+        self.assertEqual(len(inherited_perms), 2)
+
+        self.assertTrue(TEST_USER_REGULAR_LOGIN in [x.user.username
+                                                    for x in inherited_perms])
+        self.assertTrue('repository.write' in [x.permission.permission_name
+                                               for x in inherited_perms])
+
+        RepoModel().delete(repo_name_full)
+        RepoGroupModel().delete(group_name)
+        Session().commit()
+
+    def test_create_remote_repo_wrong_clone_uri(self):
+        self.log_user()
+        repo_name = self.NEW_REPO
+        description = 'description for newly created repo'
+        response = self.app.post(url('repos'),
+                        fixture._get_repo_create_params(repo_private=False,
+                                                repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
+                                                repo_description=description,
+                                                clone_uri='http://127.0.0.1/repo'))
+        response.mustcontain('invalid clone url')
+
+
+    def test_create_remote_repo_wrong_clone_uri_hg_svn(self):
+        self.log_user()
+        repo_name = self.NEW_REPO
+        description = 'description for newly created repo'
+        response = self.app.post(url('repos'),
+                        fixture._get_repo_create_params(repo_private=False,
+                                                repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
+                                                repo_description=description,
+                                                clone_uri='svn+http://127.0.0.1/repo'))
+        response.mustcontain('invalid clone url')
+
+
+    def test_delete(self):
+        self.log_user()
+        repo_name = 'vcs_test_new_to_delete_%s' % self.REPO_TYPE
+        description = 'description for newly created repo'
+        response = self.app.post(url('repos'),
+                        fixture._get_repo_create_params(repo_private=False,
+                                                repo_type=self.REPO_TYPE,
                                                 repo_name=repo_name,
                                                 repo_description=description))
+        ## run the check page that triggers the flash message
+        response = self.app.get(url('repo_check_home', repo_name=repo_name))
         self.checkSessionFlash(response,
                                'Created repository <a href="/%s">%s</a>'
                                % (repo_name, repo_name))
-
-        #test if the repo was created in the database
+        # test if the repo was created in the database
         new_repo = Session().query(Repository)\
             .filter(Repository.repo_name == repo_name).one()
 
         self.assertEqual(new_repo.repo_name, repo_name)
         self.assertEqual(new_repo.description, description)
 
-        #test if repository is visible in the list ?
-        response = response.follow()
-
-        response.mustcontain(repo_name)
-
-        #test if repository was created on filesystem
-        try:
-            vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
-        except Exception:
-            self.fail('no repo %s in filesystem' % repo_name)
-
-    def test_create_git_non_ascii(self):
-        self.log_user()
-        non_ascii = "ฤ…ฤ™ล‚"
-        repo_name = "%s%s" % (NEW_GIT_REPO, non_ascii)
-        repo_name_unicode = repo_name.decode('utf8')
-        description = 'description for newly created repo' + non_ascii
-        description_unicode = description.decode('utf8')
-        private = False
-        response = self.app.post(url('repos'),
-                        fixture._get_repo_create_params(repo_private=False,
-                                                repo_type='git',
-                                                repo_name=repo_name,
-                                                repo_description=description))
-
-        self.checkSessionFlash(response,
-                               u'Created repository <a href="/%s">%s</a>'
-                               % (urllib.quote(repo_name), repo_name_unicode))
-
-        #test if the repo was created in the database
-        new_repo = Session().query(Repository)\
-            .filter(Repository.repo_name == repo_name_unicode).one()
-
-        self.assertEqual(new_repo.repo_name, repo_name_unicode)
-        self.assertEqual(new_repo.description, description_unicode)
-
-        #test if repository is visible in the list ?
-        response = response.follow()
-
+        # test if the repository is visible in the list ?
+        response = self.app.get(url('summary_home', repo_name=repo_name))
         response.mustcontain(repo_name)
-
-        #test if repository was created on filesystem
-        try:
-            vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
-        except Exception:
-            self.fail('no repo %s in filesystem' % repo_name)
-
-    def test_update(self):
-        response = self.app.put(url('repo', repo_name=HG_REPO))
-
-    def test_update_browser_fakeout(self):
-        response = self.app.post(url('repo', repo_name=HG_REPO),
-                                 params=dict(_method='put'))
+        response.mustcontain(self.REPO_TYPE)
 
-    def test_delete_hg(self):
-        self.log_user()
-        repo_name = 'vcs_test_new_to_delete'
-        description = 'description for newly created repo'
-        response = self.app.post(url('repos'),
-                        fixture._get_repo_create_params(repo_private=False,
-                                                repo_type='hg',
-                                                repo_name=repo_name,
-                                                repo_description=description))
-
-        self.checkSessionFlash(response,
-                               'Created repository <a href="/%s">%s</a>'
-                               % (repo_name, repo_name))
-        #test if the repo was created in the database
-        new_repo = Session().query(Repository)\
-            .filter(Repository.repo_name == repo_name).one()
-
-        self.assertEqual(new_repo.repo_name, repo_name)
-        self.assertEqual(new_repo.description, description)
-
-        #test if repository is visible in the list ?
-        response = response.follow()
-
-        response.mustcontain(repo_name)
-
-        #test if repository was created on filesystem
+        # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
         except Exception:
@@ -264,47 +403,49 @@
         self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
                                   False)
 
-    def test_delete_git(self):
+    def test_delete_non_ascii(self):
         self.log_user()
-        repo_name = 'vcs_test_new_to_delete'
-        description = 'description for newly created repo'
-        private = False
+        non_ascii = "ฤ…ฤ™ล‚"
+        repo_name = "%s%s" % (self.NEW_REPO, non_ascii)
+        repo_name_unicode = repo_name.decode('utf8')
+        description = 'description for newly created repo' + non_ascii
+        description_unicode = description.decode('utf8')
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
-                                                repo_type='git',
                                                 repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
                                                 repo_description=description))
-
+        ## run the check page that triggers the flash message
+        response = self.app.get(url('repo_check_home', repo_name=repo_name))
+        self.assertEqual(response.json, {u'result': True})
         self.checkSessionFlash(response,
-                               'Created repository <a href="/%s">%s</a>'
-                               % (repo_name, repo_name))
-        #test if the repo was created in the database
+                               u'Created repository <a href="/%s">%s</a>'
+                               % (urllib.quote(repo_name), repo_name_unicode))
+        # test if the repo was created in the database
         new_repo = Session().query(Repository)\
-            .filter(Repository.repo_name == repo_name).one()
+            .filter(Repository.repo_name == repo_name_unicode).one()
 
-        self.assertEqual(new_repo.repo_name, repo_name)
-        self.assertEqual(new_repo.description, description)
+        self.assertEqual(new_repo.repo_name, repo_name_unicode)
+        self.assertEqual(new_repo.description, description_unicode)
 
-        #test if repository is visible in the list ?
-        response = response.follow()
+        # test if the repository is visible in the list ?
+        response = self.app.get(url('summary_home', repo_name=repo_name))
+        response.mustcontain(repo_name)
+        response.mustcontain(self.REPO_TYPE)
 
-        response.mustcontain(repo_name)
-
-        #test if repository was created on filesystem
+        # test if the repository was created on filesystem
         try:
             vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
         except Exception:
             self.fail('no repo %s in filesystem' % repo_name)
 
         response = self.app.delete(url('repo', repo_name=repo_name))
-
-        self.checkSessionFlash(response, 'Deleted repository %s' % (repo_name))
-
+        self.checkSessionFlash(response, 'Deleted repository %s' % (repo_name_unicode))
         response.follow()
 
         #check if repo was deleted from db
         deleted_repo = Session().query(Repository)\
-            .filter(Repository.repo_name == repo_name).scalar()
+            .filter(Repository.repo_name == repo_name_unicode).scalar()
 
         self.assertEqual(deleted_repo, None)
 
@@ -316,52 +457,49 @@
         pass
 
     def test_delete_browser_fakeout(self):
-        response = self.app.post(url('repo', repo_name=HG_REPO),
+        response = self.app.post(url('repo', repo_name=self.REPO),
                                  params=dict(_method='delete'))
 
-    def test_show_hg(self):
+    def test_show(self):
         self.log_user()
-        response = self.app.get(url('repo', repo_name=HG_REPO))
-
-    def test_show_git(self):
-        self.log_user()
-        response = self.app.get(url('repo', repo_name=GIT_REPO))
-
+        response = self.app.get(url('repo', repo_name=self.REPO))
 
     def test_edit(self):
-        response = self.app.get(url('edit_repo', repo_name=HG_REPO))
+        response = self.app.get(url('edit_repo', repo_name=self.REPO))
 
     def test_set_private_flag_sets_default_to_none(self):
         self.log_user()
         #initially repository perm should be read
-        perm = _get_permission_for_user(user='default', repo=HG_REPO)
+        perm = _get_permission_for_user(user='default', repo=self.REPO)
         self.assertTrue(len(perm), 1)
         self.assertEqual(perm[0].permission.permission_name, 'repository.read')
-        self.assertEqual(Repository.get_by_repo_name(HG_REPO).private, False)
+        self.assertEqual(Repository.get_by_repo_name(self.REPO).private, False)
 
-        response = self.app.put(url('repo', repo_name=HG_REPO),
+        response = self.app.put(url('repo', repo_name=self.REPO),
                         fixture._get_repo_create_params(repo_private=1,
-                                                repo_name=HG_REPO,
+                                                repo_name=self.REPO,
+                                                repo_type=self.REPO_TYPE,
                                                 user=TEST_USER_ADMIN_LOGIN))
         self.checkSessionFlash(response,
-                               msg='Repository %s updated successfully' % (HG_REPO))
-        self.assertEqual(Repository.get_by_repo_name(HG_REPO).private, True)
+                               msg='Repository %s updated successfully' % (self.REPO))
+        self.assertEqual(Repository.get_by_repo_name(self.REPO).private, True)
 
         #now the repo default permission should be None
-        perm = _get_permission_for_user(user='default', repo=HG_REPO)
+        perm = _get_permission_for_user(user='default', repo=self.REPO)
         self.assertTrue(len(perm), 1)
         self.assertEqual(perm[0].permission.permission_name, 'repository.none')
 
-        response = self.app.put(url('repo', repo_name=HG_REPO),
+        response = self.app.put(url('repo', repo_name=self.REPO),
                         fixture._get_repo_create_params(repo_private=False,
-                                                repo_name=HG_REPO,
+                                                repo_name=self.REPO,
+                                                repo_type=self.REPO_TYPE,
                                                 user=TEST_USER_ADMIN_LOGIN))
         self.checkSessionFlash(response,
-                               msg='Repository %s updated successfully' % (HG_REPO))
-        self.assertEqual(Repository.get_by_repo_name(HG_REPO).private, False)
+                               msg='Repository %s updated successfully' % (self.REPO))
+        self.assertEqual(Repository.get_by_repo_name(self.REPO).private, False)
 
         #we turn off private now the repo default permission should stay None
-        perm = _get_permission_for_user(user='default', repo=HG_REPO)
+        perm = _get_permission_for_user(user='default', repo=self.REPO)
         self.assertTrue(len(perm), 1)
         self.assertEqual(perm[0].permission.permission_name, 'repository.none')
 
@@ -369,3 +507,133 @@
         perm[0].permission = Permission.get_by_key('repository.read')
         Session().add(perm[0])
         Session().commit()
+
+    def test_set_repo_fork_has_no_self_id(self):
+        self.log_user()
+        repo = Repository.get_by_repo_name(self.REPO)
+        response = self.app.get(url('edit_repo_advanced', repo_name=self.REPO))
+        opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
+        response.mustcontain(no=[opt])
+
+    def test_set_fork_of_other_repo(self):
+        self.log_user()
+        other_repo = 'other_%s' % self.REPO_TYPE
+        fixture.create_repo(other_repo, repo_type=self.REPO_TYPE)
+        repo = Repository.get_by_repo_name(self.REPO)
+        repo2 = Repository.get_by_repo_name(other_repo)
+        response = self.app.put(url('edit_repo_advanced_fork', repo_name=self.REPO),
+                                params=dict(id_fork_of=repo2.repo_id))
+        repo = Repository.get_by_repo_name(self.REPO)
+        repo2 = Repository.get_by_repo_name(other_repo)
+        self.checkSessionFlash(response,
+            'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name))
+
+        assert repo.fork == repo2
+        response = response.follow()
+        # check if given repo is selected
+
+        opt = """<option value="%s" selected="selected">%s</option>""" % (
+                    repo2.repo_id, repo2.repo_name)
+        response.mustcontain(opt)
+
+        fixture.destroy_repo(other_repo, forks='detach')
+
+    def test_set_fork_of_other_type_repo(self):
+        self.log_user()
+        repo = Repository.get_by_repo_name(self.REPO)
+        repo2 = Repository.get_by_repo_name(self.OTHER_TYPE_REPO)
+        response = self.app.put(url('edit_repo_advanced_fork', repo_name=self.REPO),
+                                params=dict(id_fork_of=repo2.repo_id))
+        repo = Repository.get_by_repo_name(self.REPO)
+        repo2 = Repository.get_by_repo_name(self.OTHER_TYPE_REPO)
+        self.checkSessionFlash(response,
+            'Cannot set repository as fork of repository with other type')
+
+    def test_set_fork_of_none(self):
+        self.log_user()
+        ## mark it as None
+        response = self.app.put(url('edit_repo_advanced_fork', repo_name=self.REPO),
+                                params=dict(id_fork_of=None))
+        repo = Repository.get_by_repo_name(self.REPO)
+        repo2 = Repository.get_by_repo_name(self.OTHER_TYPE_REPO)
+        self.checkSessionFlash(response,
+                               'Marked repo %s as fork of %s'
+                               % (repo.repo_name, "Nothing"))
+        assert repo.fork is None
+
+    def test_set_fork_of_same_repo(self):
+        self.log_user()
+        repo = Repository.get_by_repo_name(self.REPO)
+        response = self.app.put(url('edit_repo_advanced_fork', repo_name=self.REPO),
+                                params=dict(id_fork_of=repo.repo_id))
+        self.checkSessionFlash(response,
+                               'An error occurred during this operation')
+
+    def test_create_on_top_level_without_permissions(self):
+        usr = self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+        # revoke
+        user_model = UserModel()
+        # disable fork and create on default user
+        user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
+        user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
+        user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
+        user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
+
+        # disable on regular user
+        user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
+        user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
+        user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
+        user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
+        Session().commit()
+
+
+        user = User.get(usr['user_id'])
+
+        repo_name = self.NEW_REPO+'no_perms'
+        description = 'description for newly created repo'
+        response = self.app.post(url('repos'),
+                        fixture._get_repo_create_params(repo_private=False,
+                                                repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
+                                                repo_description=description))
+
+        response.mustcontain('no permission to create repository in root location')
+
+        RepoModel().delete(repo_name)
+        Session().commit()
+
+    @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
+    def test_create_repo_when_filesystem_op_fails(self):
+        self.log_user()
+        repo_name = self.NEW_REPO
+        description = 'description for newly created repo'
+
+        response = self.app.post(url('repos'),
+                        fixture._get_repo_create_params(repo_private=False,
+                                                repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
+                                                repo_description=description))
+
+        self.checkSessionFlash(response,
+                               'Error creating repository %s' % repo_name)
+        # repo must not be in db
+        repo = Repository.get_by_repo_name(repo_name)
+        self.assertEqual(repo, None)
+
+        # repo must not be in filesystem !
+        self.assertFalse(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)))
+
+class TestAdminReposControllerGIT(_BaseTest):
+    REPO = GIT_REPO
+    REPO_TYPE = 'git'
+    NEW_REPO = NEW_GIT_REPO
+    OTHER_TYPE_REPO = HG_REPO
+    OTHER_TYPE = 'hg'
+
+
+class TestAdminReposControllerHG(_BaseTest):
+    REPO = HG_REPO
+    REPO_TYPE = 'hg'
+    NEW_REPO = NEW_HG_REPO
+    OTHER_TYPE_REPO = GIT_REPO
+    OTHER_TYPE = 'git'
--- a/rhodecode/tests/functional/test_admin_repos_groups.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-from rhodecode.tests import *
-
-class TestReposGroupsController(TestController):
-    pass
--- a/rhodecode/tests/functional/test_admin_settings.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_admin_settings.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,69 +1,91 @@
 # -*- coding: utf-8 -*-
 
 from rhodecode.lib.auth import get_crypt_password, check_password
-from rhodecode.model.db import User, RhodeCodeSetting, Repository
+from rhodecode.model.db import User, RhodeCodeSetting, Repository, RhodeCodeUi
 from rhodecode.tests import *
+from rhodecode.tests.fixture import Fixture
 from rhodecode.lib import helpers as h
 from rhodecode.model.user import UserModel
 from rhodecode.model.scm import ScmModel
 from rhodecode.model.meta import Session
 
+fixture = Fixture()
+
 
 class TestAdminSettingsController(TestController):
 
-    def test_index(self):
+    def test_index_main(self):
+        self.log_user()
         response = self.app.get(url('admin_settings'))
-        # Test response...
 
-    def test_index_as_xml(self):
-        response = self.app.get(url('formatted_admin_settings', format='xml'))
+    def test_index_mapping(self):
+        self.log_user()
+        response = self.app.get(url('admin_settings_mapping'))
+
+    def test_index_global(self):
+        self.log_user()
+        response = self.app.get(url('admin_settings_global'))
 
-    def test_create(self):
-        response = self.app.post(url('admin_settings'))
+    def test_index_visual(self):
+        self.log_user()
+        response = self.app.get(url('admin_settings_visual'))
 
-    def test_new(self):
-        response = self.app.get(url('admin_new_setting'))
+    def test_index_email(self):
+        self.log_user()
+        response = self.app.get(url('admin_settings_email'))
 
-    def test_new_as_xml(self):
-        response = self.app.get(url('formatted_admin_new_setting', format='xml'))
+    def test_index_hooks(self):
+        self.log_user()
+        response = self.app.get(url('admin_settings_hooks'))
 
-    def test_update(self):
-        response = self.app.put(url('admin_setting', setting_id=1))
+    def test_create_custom_hook(self):
+        self.log_user()
+        response = self.app.post(url('admin_settings_hooks'),
+                                params=dict(new_hook_ui_key='test_hooks_1',
+                                            new_hook_ui_value='cd /tmp'))
 
-    def test_update_browser_fakeout(self):
-        response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='put'))
+        response = response.follow()
+        response.mustcontain('test_hooks_1')
+        response.mustcontain('cd /tmp')
 
-    def test_delete(self):
-        response = self.app.delete(url('admin_setting', setting_id=1))
+    def test_create_custom_hook_delete(self):
+        self.log_user()
+        response = self.app.post(url('admin_settings_hooks'),
+                                params=dict(new_hook_ui_key='test_hooks_2',
+                                            new_hook_ui_value='cd /tmp2'))
 
-    def test_delete_browser_fakeout(self):
-        response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='delete'))
+        response = response.follow()
+        response.mustcontain('test_hooks_2')
+        response.mustcontain('cd /tmp2')
 
-    def test_show(self):
-        response = self.app.get(url('admin_setting', setting_id=1))
-
-    def test_show_as_xml(self):
-        response = self.app.get(url('formatted_admin_setting', setting_id=1, format='xml'))
+        hook_id = RhodeCodeUi.get_by_key('test_hooks_2').ui_id
+        ## delete
+        self.app.post(url('admin_settings_hooks'),
+                        params=dict(hook_id=hook_id))
+        response = self.app.get(url('admin_settings_hooks'))
+        response.mustcontain(no=['test_hooks_2'])
+        response.mustcontain(no=['cd /tmp2'])
 
-    def test_edit(self):
-        response = self.app.get(url('admin_edit_setting', setting_id=1))
+    def test_index_search(self):
+        self.log_user()
+        response = self.app.get(url('admin_settings_search'))
 
-    def test_edit_as_xml(self):
-        response = self.app.get(url('formatted_admin_edit_setting',
-                                    setting_id=1, format='xml'))
+    def test_index_system(self):
+        self.log_user()
+        response = self.app.get(url('admin_settings_system'))
 
     def test_ga_code_active(self):
         self.log_user()
         old_title = 'RhodeCode'
         old_realm = 'RhodeCode authentication'
         new_ga_code = 'ga-test-123456789'
-        response = self.app.post(url('admin_setting', setting_id='global'),
-                                     params=dict(
-                                                 _method='put',
-                                                 rhodecode_title=old_title,
-                                                 rhodecode_realm=old_realm,
-                                                 rhodecode_ga_code=new_ga_code
-                                                 ))
+        response = self.app.post(url('admin_settings_global'),
+                        params=dict(rhodecode_title=old_title,
+                                 rhodecode_realm=old_realm,
+                                 rhodecode_ga_code=new_ga_code,
+                                 rhodecode_captcha_private_key='',
+                                 rhodecode_captcha_public_key='',
+                                 ))
 
         self.checkSessionFlash(response, 'Updated application settings')
 
@@ -78,13 +100,13 @@
         old_title = 'RhodeCode'
         old_realm = 'RhodeCode authentication'
         new_ga_code = ''
-        response = self.app.post(url('admin_setting', setting_id='global'),
-                                     params=dict(
-                                                 _method='put',
-                                                 rhodecode_title=old_title,
-                                                 rhodecode_realm=old_realm,
-                                                 rhodecode_ga_code=new_ga_code
-                                                 ))
+        response = self.app.post(url('admin_settings_global'),
+                        params=dict(rhodecode_title=old_title,
+                                 rhodecode_realm=old_realm,
+                                 rhodecode_ga_code=new_ga_code,
+                                 rhodecode_captcha_private_key='',
+                                 rhodecode_captcha_public_key='',
+                                 ))
 
         self.checkSessionFlash(response, 'Updated application settings')
         self.assertEqual(RhodeCodeSetting
@@ -93,6 +115,46 @@
         response = response.follow()
         response.mustcontain(no=["_gaq.push(['_setAccount', '%s']);" % new_ga_code])
 
+    def test_captcha_activate(self):
+        self.log_user()
+        old_title = 'RhodeCode'
+        old_realm = 'RhodeCode authentication'
+        new_ga_code = ''
+        response = self.app.post(url('admin_settings_global'),
+                        params=dict(rhodecode_title=old_title,
+                                 rhodecode_realm=old_realm,
+                                 rhodecode_ga_code=new_ga_code,
+                                 rhodecode_captcha_private_key='1234567890',
+                                 rhodecode_captcha_public_key='1234567890',
+                                 ))
+
+        self.checkSessionFlash(response, 'Updated application settings')
+        self.assertEqual(RhodeCodeSetting
+                        .get_app_settings()['rhodecode_captcha_private_key'], '1234567890')
+
+        response = self.app.get(url('register'))
+        response.mustcontain('captcha')
+
+    def test_captcha_deactivate(self):
+        self.log_user()
+        old_title = 'RhodeCode'
+        old_realm = 'RhodeCode authentication'
+        new_ga_code = ''
+        response = self.app.post(url('admin_settings_global'),
+                        params=dict(rhodecode_title=old_title,
+                                 rhodecode_realm=old_realm,
+                                 rhodecode_ga_code=new_ga_code,
+                                 rhodecode_captcha_private_key='',
+                                 rhodecode_captcha_public_key='1234567890',
+                                 ))
+
+        self.checkSessionFlash(response, 'Updated application settings')
+        self.assertEqual(RhodeCodeSetting
+                        .get_app_settings()['rhodecode_captcha_private_key'], '')
+
+        response = self.app.get(url('register'))
+        response.mustcontain(no=['captcha'])
+
     def test_title_change(self):
         self.log_user()
         old_title = 'RhodeCode'
@@ -100,13 +162,13 @@
         old_realm = 'RhodeCode authentication'
 
         for new_title in ['Changed', 'ลปรณล‚wik', old_title]:
-            response = self.app.post(url('admin_setting', setting_id='global'),
-                                         params=dict(
-                                                     _method='put',
-                                                     rhodecode_title=new_title,
-                                                     rhodecode_realm=old_realm,
-                                                     rhodecode_ga_code=''
-                                                     ))
+            response = self.app.post(url('admin_settings_global'),
+                        params=dict(rhodecode_title=new_title,
+                                 rhodecode_realm=old_realm,
+                                 rhodecode_ga_code='',
+                                 rhodecode_captcha_private_key='',
+                                 rhodecode_captcha_public_key='',
+                                ))
 
             self.checkSessionFlash(response, 'Updated application settings')
             self.assertEqual(RhodeCodeSetting
@@ -114,152 +176,4 @@
                              new_title.decode('utf-8'))
 
             response = response.follow()
-            response.mustcontain("""<h1><a href="/">%s</a></h1>""" % new_title)
-
-    def test_my_account(self):
-        self.log_user()
-        response = self.app.get(url('admin_settings_my_account'))
-
-        response.mustcontain('value="test_admin')
-
-    @parameterized.expand([('firstname', 'new_username'),
-                           ('lastname', 'new_username'),
-                           ('admin', True),
-                           ('admin', False),
-                           ('ldap_dn', 'test'),
-                           ('ldap_dn', None),
-                           ('active', False),
-                           ('active', True),
-                           ('email', 'some@email.com'),
-                           ])
-    def test_my_account_update(self, name, expected):
-        uname = 'testme'
-        usr = UserModel().create_or_update(username=uname, password='qweqwe',
-                                           email='testme@rhodecod.org')
-        Session().commit()
-        params = usr.get_api_data()
-        user_id = usr.user_id
-        self.log_user(username=uname, password='qweqwe')
-        params.update({name: expected})
-        params.update({'password_confirmation': ''})
-        params.update({'new_password': ''})
-
-        try:
-            response = self.app.put(url('admin_settings_my_account_update',
-                                        id=user_id), params)
-
-            self.checkSessionFlash(response,
-                                   'Your account was updated successfully')
-
-            updated_user = User.get_by_username(uname)
-            updated_params = updated_user.get_api_data()
-            updated_params.update({'password_confirmation': ''})
-            updated_params.update({'new_password': ''})
-
-            params['last_login'] = updated_params['last_login']
-            if name == 'email':
-                params['emails'] = [expected]
-            if name == 'ldap_dn':
-                #cannot update this via form
-                params['ldap_dn'] = None
-            if name == 'active':
-                #my account cannot deactivate account
-                params['active'] = True
-            if name == 'admin':
-                #my account cannot make you an admin !
-                params['admin'] = False
-
-            self.assertEqual(params, updated_params)
-
-        finally:
-            UserModel().delete('testme')
-
-    def test_my_account_update_err_email_exists(self):
-        self.log_user()
-
-        new_email = 'test_regular@mail.com'  # already exisitn email
-        response = self.app.put(url('admin_settings_my_account_update'),
-                                params=dict(
-                                    username='test_admin',
-                                    new_password='test12',
-                                    password_confirmation='test122',
-                                    firstname='NewName',
-                                    lastname='NewLastname',
-                                    email=new_email,)
-                                )
-
-        response.mustcontain('This e-mail address is already taken')
-
-    def test_my_account_update_err(self):
-        self.log_user('test_regular2', 'test12')
-
-        new_email = 'newmail.pl'
-        response = self.app.post(url('admin_settings_my_account_update'),
-                                 params=dict(
-                                            _method='put',
-                                            username='test_admin',
-                                            new_password='test12',
-                                            password_confirmation='test122',
-                                            firstname='NewName',
-                                            lastname='NewLastname',
-                                            email=new_email,)
-                                 )
-
-        response.mustcontain('An email address must contain a single @')
-        from rhodecode.model import validators
-        msg = validators.ValidUsername(edit=False,
-                                    old_data={})._messages['username_exists']
-        msg = h.html_escape(msg % {'username': 'test_admin'})
-        response.mustcontain(u"%s" % msg)
-
-    def test_set_repo_fork_has_no_self_id(self):
-        self.log_user()
-        repo = Repository.get_by_repo_name(HG_REPO)
-        response = self.app.get(url('edit_repo', repo_name=HG_REPO))
-        opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
-        response.mustcontain(no=[opt])
-
-    def test_set_fork_of_repo(self):
-        self.log_user()
-        repo = Repository.get_by_repo_name(HG_REPO)
-        repo2 = Repository.get_by_repo_name(GIT_REPO)
-        response = self.app.put(url('repo_as_fork', repo_name=HG_REPO),
-                                 params=dict(
-                                    id_fork_of=repo2.repo_id
-                                 ))
-        repo = Repository.get_by_repo_name(HG_REPO)
-        repo2 = Repository.get_by_repo_name(GIT_REPO)
-        self.checkSessionFlash(response,
-        'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name))
-
-        assert repo.fork == repo2
-        response = response.follow()
-        # check if given repo is selected
-
-        opt = """<option value="%s" selected="selected">%s</option>""" % (
-                                                repo2.repo_id, repo2.repo_name)
-        response.mustcontain(opt)
-
-        # clean session flash
-        #response = self.app.get(url('edit_repo', repo_name=HG_REPO))
-
-        ## mark it as None
-        response = self.app.put(url('repo_as_fork', repo_name=HG_REPO),
-                                 params=dict(
-                                    id_fork_of=None
-                                 ))
-        repo = Repository.get_by_repo_name(HG_REPO)
-        repo2 = Repository.get_by_repo_name(GIT_REPO)
-        self.checkSessionFlash(response,
-        'Marked repo %s as fork of %s' % (repo.repo_name, "Nothing"))
-        assert repo.fork is None
-
-    def test_set_fork_of_same_repo(self):
-        self.log_user()
-        repo = Repository.get_by_repo_name(HG_REPO)
-        response = self.app.put(url('repo_as_fork', repo_name=HG_REPO),
-                                 params=dict(
-                                    id_fork_of=repo.repo_id
-                                 ))
-        self.checkSessionFlash(response,
-                               'An error occurred during this operation')
+            response.mustcontain("""<div class="branding">- %s</div>""" % new_title)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/functional/test_admin_user_groups.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,220 @@
+# -*- coding: utf-8 -*-
+from rhodecode.tests import *
+from rhodecode.model.db import UserGroup, UserGroupToPerm, Permission
+from rhodecode.model.meta import Session
+
+TEST_USER_GROUP = 'admins_test'
+
+
+class TestAdminUsersGroupsController(TestController):
+
+    def test_index(self):
+        self.log_user()
+        response = self.app.get(url('users_groups'))
+        # Test response...
+
+    def test_create(self):
+        self.log_user()
+        users_group_name = TEST_USER_GROUP
+        response = self.app.post(url('users_groups'),
+                                 {'users_group_name': users_group_name,
+                                  'user_group_description': 'DESC',
+                                  'active': True})
+        response.follow()
+
+        self.checkSessionFlash(response,
+                               'Created user group %s' % TEST_USER_GROUP)
+
+    def test_new(self):
+        response = self.app.get(url('new_users_group'))
+
+    def test_update(self):
+        response = self.app.put(url('users_group', id=1))
+
+    def test_update_browser_fakeout(self):
+        response = self.app.post(url('users_group', id=1),
+                                 params=dict(_method='put'))
+
+    def test_delete(self):
+        self.log_user()
+        users_group_name = TEST_USER_GROUP + 'another'
+        response = self.app.post(url('users_groups'),
+                                 {'users_group_name':users_group_name,
+                                  'user_group_description': 'DESC',
+                                  'active': True})
+        response.follow()
+
+        self.checkSessionFlash(response,
+                               'Created user group %s' % users_group_name)
+
+        gr = Session().query(UserGroup)\
+            .filter(UserGroup.users_group_name == users_group_name).one()
+
+        response = self.app.delete(url('users_group', id=gr.users_group_id))
+
+        gr = Session().query(UserGroup)\
+            .filter(UserGroup.users_group_name == users_group_name).scalar()
+
+        self.assertEqual(gr, None)
+
+    def test_default_perms_enable_repository_read_on_group(self):
+        self.log_user()
+        users_group_name = TEST_USER_GROUP + 'another2'
+        response = self.app.post(url('users_groups'),
+                                 {'users_group_name': users_group_name,
+                                  'user_group_description': 'DESC',
+                                  'active': True})
+        response.follow()
+
+        ug = UserGroup.get_by_group_name(users_group_name)
+        self.checkSessionFlash(response,
+                               'Created user group %s' % users_group_name)
+        ## ENABLE REPO CREATE ON A GROUP
+        response = self.app.put(url('edit_user_group_default_perms',
+                                    id=ug.users_group_id),
+                                 {'create_repo_perm': True})
+
+        response.follow()
+        ug = UserGroup.get_by_group_name(users_group_name)
+        p = Permission.get_by_key('hg.create.repository')
+        p2 = Permission.get_by_key('hg.usergroup.create.false')
+        p3 = Permission.get_by_key('hg.fork.none')
+        # check if user has this perms, they should be here since
+        # defaults are on
+        perms = UserGroupToPerm.query()\
+            .filter(UserGroupToPerm.users_group == ug).all()
+
+        self.assertEqual(
+            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
+            sorted([[ug.users_group_id, p.permission_id],
+                    [ug.users_group_id, p2.permission_id],
+                    [ug.users_group_id, p3.permission_id]]))
+
+        ## DISABLE REPO CREATE ON A GROUP
+        response = self.app.put(
+            url('edit_user_group_default_perms', id=ug.users_group_id), {})
+
+        response.follow()
+        ug = UserGroup.get_by_group_name(users_group_name)
+        p = Permission.get_by_key('hg.create.none')
+        p2 = Permission.get_by_key('hg.usergroup.create.false')
+        p3 = Permission.get_by_key('hg.fork.none')
+
+        # check if user has this perms, they should be here since
+        # defaults are on
+        perms = UserGroupToPerm.query()\
+            .filter(UserGroupToPerm.users_group == ug).all()
+
+        self.assertEqual(
+            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
+            sorted([[ug.users_group_id, p.permission_id],
+                    [ug.users_group_id, p2.permission_id],
+                    [ug.users_group_id, p3.permission_id]]))
+
+        # DELETE !
+        ug = UserGroup.get_by_group_name(users_group_name)
+        ugid = ug.users_group_id
+        response = self.app.delete(url('users_group', id=ug.users_group_id))
+        response = response.follow()
+        gr = Session().query(UserGroup)\
+            .filter(UserGroup.users_group_name == users_group_name).scalar()
+
+        self.assertEqual(gr, None)
+        p = Permission.get_by_key('hg.create.repository')
+        perms = UserGroupToPerm.query()\
+            .filter(UserGroupToPerm.users_group_id == ugid).all()
+        perms = [[x.users_group_id,
+                  x.permission_id, ] for x in perms]
+        self.assertEqual(perms, [])
+
+    def test_default_perms_enable_repository_fork_on_group(self):
+        self.log_user()
+        users_group_name = TEST_USER_GROUP + 'another2'
+        response = self.app.post(url('users_groups'),
+                                 {'users_group_name': users_group_name,
+                                  'user_group_description': 'DESC',
+                                  'active': True})
+        response.follow()
+
+        ug = UserGroup.get_by_group_name(users_group_name)
+        self.checkSessionFlash(response,
+                               'Created user group %s' % users_group_name)
+        ## ENABLE REPO CREATE ON A GROUP
+        response = self.app.put(url('edit_user_group_default_perms',
+                                    id=ug.users_group_id),
+                                {'fork_repo_perm': True})
+
+        response.follow()
+        ug = UserGroup.get_by_group_name(users_group_name)
+        p = Permission.get_by_key('hg.create.none')
+        p2 = Permission.get_by_key('hg.usergroup.create.false')
+        p3 = Permission.get_by_key('hg.fork.repository')
+        # check if user has this perms, they should be here since
+        # defaults are on
+        perms = UserGroupToPerm.query()\
+            .filter(UserGroupToPerm.users_group == ug).all()
+
+        self.assertEqual(
+            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
+            sorted([[ug.users_group_id, p.permission_id],
+                    [ug.users_group_id, p2.permission_id],
+                    [ug.users_group_id, p3.permission_id]]))
+
+        ## DISABLE REPO CREATE ON A GROUP
+        response = self.app.put(
+            url('edit_user_group_default_perms', id=ug.users_group_id), {})
+
+        response.follow()
+        ug = UserGroup.get_by_group_name(users_group_name)
+        p = Permission.get_by_key('hg.create.none')
+        p2 = Permission.get_by_key('hg.usergroup.create.false')
+        p3 = Permission.get_by_key('hg.fork.none')
+        # check if user has this perms, they should be here since
+        # defaults are on
+        perms = UserGroupToPerm.query()\
+            .filter(UserGroupToPerm.users_group == ug).all()
+
+        self.assertEqual(
+            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
+            sorted([[ug.users_group_id, p.permission_id],
+                    [ug.users_group_id, p2.permission_id],
+                    [ug.users_group_id, p3.permission_id]]))
+
+        # DELETE !
+        ug = UserGroup.get_by_group_name(users_group_name)
+        ugid = ug.users_group_id
+        response = self.app.delete(url('users_group', id=ug.users_group_id))
+        response = response.follow()
+        gr = Session().query(UserGroup)\
+                           .filter(UserGroup.users_group_name ==
+                                   users_group_name).scalar()
+
+        self.assertEqual(gr, None)
+        p = Permission.get_by_key('hg.fork.repository')
+        perms = UserGroupToPerm.query()\
+            .filter(UserGroupToPerm.users_group_id == ugid).all()
+        perms = [[x.users_group_id,
+                  x.permission_id, ] for x in perms]
+        self.assertEqual(
+            perms,
+            []
+        )
+
+    def test_delete_browser_fakeout(self):
+        response = self.app.post(url('users_group', id=1),
+                                 params=dict(_method='delete'))
+
+    def test_show(self):
+        response = self.app.get(url('users_group', id=1))
+
+    def test_edit(self):
+        response = self.app.get(url('edit_users_group', id=1))
+
+    def test_assign_members(self):
+        pass
+
+    def test_add_create_permission(self):
+        pass
+
+    def test_revoke_members(self):
+        pass
--- a/rhodecode/tests/functional/test_admin_users.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_admin_users.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,24 +1,45 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 from sqlalchemy.orm.exc import NoResultFound
 
 from rhodecode.tests import *
-from rhodecode.model.db import User, Permission
+from rhodecode.tests.fixture import Fixture
+from rhodecode.model.db import User, Permission, UserIpMap, UserApiKeys
 from rhodecode.lib.auth import check_password
 from rhodecode.model.user import UserModel
 from rhodecode.model import validators
 from rhodecode.lib import helpers as h
 from rhodecode.model.meta import Session
 
+fixture = Fixture()
+
 
 class TestAdminUsersController(TestController):
+    test_user_1 = 'testme'
+
+    @classmethod
+    def teardown_class(cls):
+        if User.get_by_username(cls.test_user_1):
+            UserModel().delete(cls.test_user_1)
+            Session().commit()
 
     def test_index(self):
         self.log_user()
         response = self.app.get(url('users'))
         # Test response...
 
-    def test_index_as_xml(self):
-        response = self.app.get(url('formatted_users', format='xml'))
-
     def test_create(self):
         self.log_user()
         username = 'newtestuser'
@@ -29,13 +50,15 @@
         email = 'mail@mail.com'
 
         response = self.app.post(url('users'),
-                             {'username': username,
-                               'password': password,
-                               'password_confirmation': password_confirmation,
-                               'firstname': name,
-                               'active': True,
-                               'lastname': lastname,
-                               'email': email})
+            {'username': username,
+             'password': password,
+             'password_confirmation': password_confirmation,
+             'firstname': name,
+             'active': True,
+             'lastname': lastname,
+             'extern_name': 'rhodecode',
+             'extern_type': 'rhodecode',
+             'email': email})
 
         self.checkSessionFlash(response, '''Created user %s''' % (username))
 
@@ -82,69 +105,60 @@
         self.log_user()
         response = self.app.get(url('new_user'))
 
-    def test_new_as_xml(self):
-        response = self.app.get(url('formatted_new_user', format='xml'))
-
-    @parameterized.expand([('firstname', 'new_username'),
-                           ('lastname', 'new_username'),
-                           ('admin', True),
-                           ('admin', False),
-                           ('ldap_dn', 'test'),
-                           ('ldap_dn', None),
-                           ('active', False),
-                           ('active', True),
-                           ('email', 'some@email.com'),
-                           ])
-    def test_update(self, name, expected):
+    @parameterized.expand(
+        [('firstname', {'firstname': 'new_username'}),
+         ('lastname', {'lastname': 'new_username'}),
+         ('admin', {'admin': True}),
+         ('admin', {'admin': False}),
+         ('extern_type', {'extern_type': 'ldap'}),
+         ('extern_type', {'extern_type': None}),
+         ('extern_name', {'extern_name': 'test'}),
+         ('extern_name', {'extern_name': None}),
+         ('active', {'active': False}),
+         ('active', {'active': True}),
+         ('email', {'email': 'some@email.com'}),
+        # ('new_password', {'new_password': 'foobar123',
+        #                   'password_confirmation': 'foobar123'})
+        ])
+    def test_update(self, name, attrs):
         self.log_user()
-        uname = 'testme'
-        usr = UserModel().create_or_update(username=uname, password='qweqwe',
-                                           email='testme@rhodecod.org')
+        usr = fixture.create_user(self.test_user_1, password='qweqwe',
+                                  email='testme@rhodecode.org',
+                                  extern_type='rhodecode',
+                                  extern_name=self.test_user_1,
+                                  skip_if_exists=True)
         Session().commit()
         params = usr.get_api_data()
-        params.update({name: expected})
         params.update({'password_confirmation': ''})
         params.update({'new_password': ''})
+        params.update(attrs)
         if name == 'email':
-            params['emails'] = [expected]
-        if name == 'ldap_dn':
-            #cannot update this via form
-            params['ldap_dn'] = None
-        try:
-            response = self.app.put(url('user', id=usr.user_id), params)
-
-            self.checkSessionFlash(response, '''User updated successfully''')
+            params['emails'] = [attrs['email']]
+        if name == 'extern_type':
+            #cannot update this via form, expected value is original one
+            params['extern_type'] = "rhodecode"
+        if name == 'extern_name':
+            #cannot update this via form, expected value is original one
+            params['extern_name'] = self.test_user_1
+            # special case since this user is not
+                                          # logged in yet his data is not filled
+                                          # so we use creation data
 
-            updated_user = User.get_by_username(uname)
-            updated_params = updated_user.get_api_data()
-            updated_params.update({'password_confirmation': ''})
-            updated_params.update({'new_password': ''})
+        response = self.app.put(url('user', id=usr.user_id), params)
+        self.checkSessionFlash(response, 'User updated successfully')
 
-            self.assertEqual(params, updated_params)
+        updated_user = User.get_by_username(self.test_user_1)
+        updated_params = updated_user.get_api_data()
+        updated_params.update({'password_confirmation': ''})
+        updated_params.update({'new_password': ''})
 
-        finally:
-            UserModel().delete('testme')
-
-    def test_update_browser_fakeout(self):
-        response = self.app.post(url('user', id=1), params=dict(_method='put'))
+        self.assertEqual(params, updated_params)
 
     def test_delete(self):
         self.log_user()
         username = 'newtestuserdeleteme'
-        password = 'test12'
-        name = 'name'
-        lastname = 'lastname'
-        email = 'todeletemail@mail.com'
 
-        response = self.app.post(url('users'), {'username': username,
-                                               'password': password,
-                                               'password_confirmation': password,
-                                               'firstname': name,
-                                               'active': True,
-                                               'lastname': lastname,
-                                               'email': email})
-
-        response = response.follow()
+        fixture.create_user(name=username)
 
         new_user = Session().query(User)\
             .filter(User.username == username).one()
@@ -152,16 +166,9 @@
 
         self.checkSessionFlash(response, 'Successfully deleted user')
 
-    def test_delete_browser_fakeout(self):
-        response = self.app.post(url('user', id=1),
-                                 params=dict(_method='delete'))
-
     def test_show(self):
         response = self.app.get(url('user', id=1))
 
-    def test_show_as_xml(self):
-        response = self.app.get(url('formatted_user', id=1, format='xml'))
-
     def test_edit(self):
         self.log_user()
         user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
@@ -183,7 +190,7 @@
             self.assertEqual(UserModel().has_perm(user, perm_none), False)
             self.assertEqual(UserModel().has_perm(user, perm_create), False)
 
-            response = self.app.post(url('user_perm', id=uid),
+            response = self.app.post(url('edit_user_perms', id=uid),
                                      params=dict(_method='put',
                                                  create_repo_perm=True))
 
@@ -213,7 +220,7 @@
             self.assertEqual(UserModel().has_perm(user, perm_none), False)
             self.assertEqual(UserModel().has_perm(user, perm_create), False)
 
-            response = self.app.post(url('user_perm', id=uid),
+            response = self.app.post(url('edit_user_perms', id=uid),
                                      params=dict(_method='put'))
 
             perm_none = Permission.get_by_key('hg.create.none')
@@ -242,7 +249,7 @@
             self.assertEqual(UserModel().has_perm(user, perm_none), False)
             self.assertEqual(UserModel().has_perm(user, perm_fork), False)
 
-            response = self.app.post(url('user_perm', id=uid),
+            response = self.app.post(url('edit_user_perms', id=uid),
                                      params=dict(_method='put',
                                                  create_repo_perm=True))
 
@@ -272,7 +279,7 @@
             self.assertEqual(UserModel().has_perm(user, perm_none), False)
             self.assertEqual(UserModel().has_perm(user, perm_fork), False)
 
-            response = self.app.post(url('user_perm', id=uid),
+            response = self.app.post(url('edit_user_perms', id=uid),
                                      params=dict(_method='put'))
 
             perm_none = Permission.get_by_key('hg.create.none')
@@ -285,5 +292,128 @@
             UserModel().delete(uid)
             Session().commit()
 
-    def test_edit_as_xml(self):
-        response = self.app.get(url('formatted_edit_user', id=1, format='xml'))
+    def test_ips(self):
+        self.log_user()
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        response = self.app.get(url('edit_user_ips', id=user.user_id))
+        response.mustcontain('All IP addresses are allowed')
+
+    @parameterized.expand([
+        ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
+        ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
+        ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
+        ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
+        ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
+        ('127_bad_ip', 'foobar', 'foobar', True),
+    ])
+    def test_add_ip(self, test_name, ip, ip_range, failure):
+        self.log_user()
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user_id = user.user_id
+
+        response = self.app.put(url('edit_user_ips', id=user_id),
+                                params=dict(new_ip=ip))
+
+        if failure:
+            self.checkSessionFlash(response, 'Please enter a valid IPv4 or IpV6 address')
+            response = self.app.get(url('edit_user_ips', id=user_id))
+            response.mustcontain(no=[ip])
+            response.mustcontain(no=[ip_range])
+
+        else:
+            response = self.app.get(url('edit_user_ips', id=user_id))
+            response.mustcontain(ip)
+            response.mustcontain(ip_range)
+
+        ## cleanup
+        for del_ip in UserIpMap.query().filter(UserIpMap.user_id == user_id).all():
+            Session().delete(del_ip)
+            Session().commit()
+
+    def test_delete_ip(self):
+        self.log_user()
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user_id = user.user_id
+        ip = '127.0.0.1/32'
+        ip_range = '127.0.0.1 - 127.0.0.1'
+        new_ip = UserModel().add_extra_ip(user_id, ip)
+        Session().commit()
+        new_ip_id = new_ip.ip_id
+
+        response = self.app.get(url('edit_user_ips', id=user_id))
+        response.mustcontain(ip)
+        response.mustcontain(ip_range)
+
+        self.app.post(url('edit_user_ips', id=user_id),
+                      params=dict(_method='delete', del_ip_id=new_ip_id))
+
+        response = self.app.get(url('edit_user_ips', id=user_id))
+        response.mustcontain('All IP addresses are allowed')
+        response.mustcontain(no=[ip])
+        response.mustcontain(no=[ip_range])
+
+    def test_api_keys(self):
+        self.log_user()
+
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        response = self.app.get(url('edit_user_api_keys', id=user.user_id))
+        response.mustcontain(user.api_key)
+        response.mustcontain('expires: never')
+
+    @parameterized.expand([
+        ('forever', -1),
+        ('5mins', 60*5),
+        ('30days', 60*60*24*30),
+    ])
+    def test_add_api_keys(self, desc, lifetime):
+        self.log_user()
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user_id = user.user_id
+
+        response = self.app.post(url('edit_user_api_keys', id=user_id),
+                 {'_method': 'put', 'description': desc, 'lifetime': lifetime})
+        self.checkSessionFlash(response, 'Api key successfully created')
+        try:
+            response = response.follow()
+            user = User.get(user_id)
+            for api_key in user.api_keys:
+                response.mustcontain(api_key)
+        finally:
+            for api_key in UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all():
+                Session().delete(api_key)
+                Session().commit()
+
+    def test_remove_api_key(self):
+        self.log_user()
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user_id = user.user_id
+
+        response = self.app.post(url('edit_user_api_keys', id=user_id),
+                {'_method': 'put', 'description': 'desc', 'lifetime': -1})
+        self.checkSessionFlash(response, 'Api key successfully created')
+        response = response.follow()
+
+        #now delete our key
+        keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
+        self.assertEqual(1, len(keys))
+
+        response = self.app.post(url('edit_user_api_keys', id=user_id),
+                 {'_method': 'delete', 'del_api_key': keys[0].api_key})
+        self.checkSessionFlash(response, 'Api key successfully deleted')
+        keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
+        self.assertEqual(0, len(keys))
+
+    def test_reset_main_api_key(self):
+        self.log_user()
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user_id = user.user_id
+        api_key = user.api_key
+        response = self.app.get(url('edit_user_api_keys', id=user_id))
+        response.mustcontain(api_key)
+        response.mustcontain('expires: never')
+
+        response = self.app.post(url('edit_user_api_keys', id=user_id),
+                 {'_method': 'delete', 'del_api_key_builtin': api_key})
+        self.checkSessionFlash(response, 'Api key successfully reset')
+        response = response.follow()
+        response.mustcontain(no=[api_key])
--- a/rhodecode/tests/functional/test_admin_users_groups.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,234 +0,0 @@
-from rhodecode.tests import *
-from rhodecode.model.db import UserGroup, UserGroupToPerm, Permission
-from rhodecode.model.meta import Session
-
-TEST_USER_GROUP = 'admins_test'
-
-
-class TestAdminUsersGroupsController(TestController):
-
-    def test_index(self):
-        response = self.app.get(url('users_groups'))
-        # Test response...
-
-    def test_index_as_xml(self):
-        response = self.app.get(url('formatted_users_groups', format='xml'))
-
-    def test_create(self):
-        self.log_user()
-        users_group_name = TEST_USER_GROUP
-        response = self.app.post(url('users_groups'),
-                                 {'users_group_name': users_group_name,
-                                  'active':True})
-        response.follow()
-
-        self.checkSessionFlash(response,
-                               'Created user group %s' % TEST_USER_GROUP)
-
-    def test_new(self):
-        response = self.app.get(url('new_users_group'))
-
-    def test_new_as_xml(self):
-        response = self.app.get(url('formatted_new_users_group', format='xml'))
-
-    def test_update(self):
-        response = self.app.put(url('users_group', id=1))
-
-    def test_update_browser_fakeout(self):
-        response = self.app.post(url('users_group', id=1),
-                                 params=dict(_method='put'))
-
-    def test_delete(self):
-        self.log_user()
-        users_group_name = TEST_USER_GROUP + 'another'
-        response = self.app.post(url('users_groups'),
-                                 {'users_group_name':users_group_name,
-                                  'active':True})
-        response.follow()
-
-        self.checkSessionFlash(response,
-                               'Created user group %s' % users_group_name)
-
-        gr = Session().query(UserGroup)\
-                           .filter(UserGroup.users_group_name ==
-                                   users_group_name).one()
-
-        response = self.app.delete(url('users_group', id=gr.users_group_id))
-
-        gr = Session().query(UserGroup)\
-                           .filter(UserGroup.users_group_name ==
-                                   users_group_name).scalar()
-
-        self.assertEqual(gr, None)
-
-    def test_enable_repository_read_on_group(self):
-        self.log_user()
-        users_group_name = TEST_USER_GROUP + 'another2'
-        response = self.app.post(url('users_groups'),
-                                 {'users_group_name': users_group_name,
-                                  'active': True})
-        response.follow()
-
-        ug = UserGroup.get_by_group_name(users_group_name)
-        self.checkSessionFlash(response,
-                               'Created user group %s' % users_group_name)
-        ## ENABLE REPO CREATE ON A GROUP
-        response = self.app.put(url('users_group_perm', id=ug.users_group_id),
-                                 {'create_repo_perm': True})
-
-        response.follow()
-        ug = UserGroup.get_by_group_name(users_group_name)
-        p = Permission.get_by_key('hg.create.repository')
-        p2 = Permission.get_by_key('hg.usergroup.create.false')
-        p3 = Permission.get_by_key('hg.fork.none')
-        # check if user has this perms, they should be here since
-        # defaults are on
-        perms = UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group == ug).all()
-
-        self.assertEqual(
-            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
-            sorted([[ug.users_group_id, p.permission_id],
-                    [ug.users_group_id, p2.permission_id],
-                    [ug.users_group_id, p3.permission_id]])
-        )
-
-        ## DISABLE REPO CREATE ON A GROUP
-        response = self.app.put(url('users_group_perm', id=ug.users_group_id),
-                                    {})
-
-        response.follow()
-        ug = UserGroup.get_by_group_name(users_group_name)
-        p = Permission.get_by_key('hg.create.none')
-        p2 = Permission.get_by_key('hg.usergroup.create.false')
-        p3 = Permission.get_by_key('hg.fork.none')
-
-        # check if user has this perms, they should be here since
-        # defaults are on
-        perms = UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group == ug).all()
-
-        self.assertEqual(
-            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
-            sorted([[ug.users_group_id, p.permission_id],
-                    [ug.users_group_id, p2.permission_id],
-                    [ug.users_group_id, p3.permission_id]])
-        )
-
-        # DELETE !
-        ug = UserGroup.get_by_group_name(users_group_name)
-        ugid = ug.users_group_id
-        response = self.app.delete(url('users_group', id=ug.users_group_id))
-        response = response.follow()
-        gr = Session().query(UserGroup)\
-                           .filter(UserGroup.users_group_name ==
-                                   users_group_name).scalar()
-
-        self.assertEqual(gr, None)
-        p = Permission.get_by_key('hg.create.repository')
-        perms = UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group_id == ugid).all()
-        perms = [[x.users_group_id,
-                  x.permission_id, ] for x in perms]
-        self.assertEqual(
-            perms,
-            []
-        )
-
-    def test_enable_repository_fork_on_group(self):
-        self.log_user()
-        users_group_name = TEST_USER_GROUP + 'another2'
-        response = self.app.post(url('users_groups'),
-                                 {'users_group_name': users_group_name,
-                                  'active': True})
-        response.follow()
-
-        ug = UserGroup.get_by_group_name(users_group_name)
-        self.checkSessionFlash(response,
-                               'Created user group %s' % users_group_name)
-        ## ENABLE REPO CREATE ON A GROUP
-        response = self.app.put(url('users_group_perm', id=ug.users_group_id),
-                                 {'fork_repo_perm': True})
-
-        response.follow()
-        ug = UserGroup.get_by_group_name(users_group_name)
-        p = Permission.get_by_key('hg.create.none')
-        p2 = Permission.get_by_key('hg.usergroup.create.false')
-        p3 = Permission.get_by_key('hg.fork.repository')
-        # check if user has this perms, they should be here since
-        # defaults are on
-        perms = UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group == ug).all()
-
-        self.assertEqual(
-            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
-            sorted([[ug.users_group_id, p.permission_id],
-                    [ug.users_group_id, p2.permission_id],
-                    [ug.users_group_id, p3.permission_id]])
-        )
-
-        ## DISABLE REPO CREATE ON A GROUP
-        response = self.app.put(url('users_group_perm', id=ug.users_group_id),
-                                    {})
-
-        response.follow()
-        ug = UserGroup.get_by_group_name(users_group_name)
-        p = Permission.get_by_key('hg.create.none')
-        p2 = Permission.get_by_key('hg.usergroup.create.false')
-        p3 = Permission.get_by_key('hg.fork.none')
-        # check if user has this perms, they should be here since
-        # defaults are on
-        perms = UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group == ug).all()
-
-        self.assertEqual(
-            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
-            sorted([[ug.users_group_id, p.permission_id],
-                    [ug.users_group_id, p2.permission_id],
-                    [ug.users_group_id, p3.permission_id]])
-        )
-
-        # DELETE !
-        ug = UserGroup.get_by_group_name(users_group_name)
-        ugid = ug.users_group_id
-        response = self.app.delete(url('users_group', id=ug.users_group_id))
-        response = response.follow()
-        gr = Session().query(UserGroup)\
-                           .filter(UserGroup.users_group_name ==
-                                   users_group_name).scalar()
-
-        self.assertEqual(gr, None)
-        p = Permission.get_by_key('hg.fork.repository')
-        perms = UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group_id == ugid).all()
-        perms = [[x.users_group_id,
-                  x.permission_id, ] for x in perms]
-        self.assertEqual(
-            perms,
-            []
-        )
-
-    def test_delete_browser_fakeout(self):
-        response = self.app.post(url('users_group', id=1),
-                                 params=dict(_method='delete'))
-
-    def test_show(self):
-        response = self.app.get(url('users_group', id=1))
-
-    def test_show_as_xml(self):
-        response = self.app.get(url('formatted_users_group', id=1, format='xml'))
-
-    def test_edit(self):
-        response = self.app.get(url('edit_users_group', id=1))
-
-    def test_edit_as_xml(self):
-        response = self.app.get(url('formatted_edit_users_group', id=1, format='xml'))
-
-    def test_assign_members(self):
-        pass
-
-    def test_add_create_permission(self):
-        pass
-
-    def test_revoke_members(self):
-        pass
--- a/rhodecode/tests/functional/test_compare.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_compare.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 from rhodecode.tests import *
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.meta import Session
@@ -40,6 +41,10 @@
     return cs
 
 
+def _commit_div(sha, msg):
+    return """<div id="C-%s" class="message" style="white-space:normal; height: 10px;width: 250px">%s</div>""" % (sha, msg)
+
+
 class TestCompareController(TestController):
 
     def setUp(self):
@@ -78,29 +83,77 @@
         rev1 = 'default'
         rev2 = 'default'
 
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=repo1.repo_name,
                                     org_ref_type="branch",
                                     org_ref=rev2,
                                     other_repo=repo2.repo_name,
                                     other_ref_type="branch",
                                     other_ref=rev1,
-                                    merge='1',
-                                    ))
+                                    merge='1',))
 
-        response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, rev2, repo2.repo_name, rev1))
+        response.mustcontain('%s@%s' % (repo1.repo_name, rev2))
+        response.mustcontain('%s@%s' % (repo2.repo_name, rev1))
         response.mustcontain("""Showing 2 commits""")
         response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
 
-        response.mustcontain("""<div class="message tooltip" title="commit2" style="white-space:normal">commit2</div>""")
-        response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
+        response.mustcontain(_commit_div(cs1.raw_id, 'commit2'))
+        response.mustcontain(_commit_div(cs2.raw_id, 'commit3'))
 
         response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
         response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
         ## files
         response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=1#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
         #swap
-        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
+        response.mustcontain("""<a class="btn btn-small" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-refresh"></i> Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
+
+    def test_compare_forks_on_branch_extra_commits_git(self):
+        self.log_user()
+        repo1 = fixture.create_repo('one-git', repo_type='git',
+                                    repo_description='diff-test',
+                                    cur_user=TEST_USER_ADMIN_LOGIN)
+        self.r1_id = repo1.repo_id
+        #commit something !
+        cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
+                             message='commit1', vcs_type='git', parent=None, newfile=True)
+
+        #fork this repo
+        repo2 = fixture.create_fork('one-git', 'one-git-fork')
+        self.r2_id = repo2.repo_id
+
+        #add two extra commit into fork
+        cs1 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\n',
+                             message='commit2', vcs_type='git', parent=cs0)
+
+        cs2 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
+                             message='commit3', vcs_type='git', parent=cs1)
+
+        rev1 = 'master'
+        rev2 = 'master'
+
+        response = self.app.get(url('compare_url',
+                                    repo_name=repo1.repo_name,
+                                    org_ref_type="branch",
+                                    org_ref=rev2,
+                                    other_repo=repo2.repo_name,
+                                    other_ref_type="branch",
+                                    other_ref=rev1,
+                                    merge='1',))
+
+        response.mustcontain('%s@%s' % (repo1.repo_name, rev2))
+        response.mustcontain('%s@%s' % (repo2.repo_name, rev1))
+        response.mustcontain("""Showing 2 commits""")
+        response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
+
+        response.mustcontain(_commit_div(cs1.raw_id, 'commit2'))
+        response.mustcontain(_commit_div(cs2.raw_id, 'commit3'))
+
+        response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
+        response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
+        ## files
+        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=1#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
+        #swap
+        response.mustcontain("""<a class="btn btn-small" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-refresh"></i> Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
 
     def test_compare_forks_on_branch_extra_commits_origin_has_incomming_hg(self):
         self.log_user()
@@ -133,28 +186,84 @@
         rev1 = 'default'
         rev2 = 'default'
 
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=repo1.repo_name,
                                     org_ref_type="branch",
                                     org_ref=rev2,
                                     other_repo=repo2.repo_name,
                                     other_ref_type="branch",
                                     other_ref=rev1,
-                                    merge='x',
-                                    ))
-        response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, rev2, repo2.repo_name, rev1))
+                                    merge='1',))
+
+        response.mustcontain('%s@%s' % (repo1.repo_name, rev2))
+        response.mustcontain('%s@%s' % (repo2.repo_name, rev1))
         response.mustcontain("""Showing 2 commits""")
         response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
 
-        response.mustcontain("""<div class="message tooltip" title="commit2" style="white-space:normal">commit2</div>""")
-        response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
+        response.mustcontain(_commit_div(cs1.raw_id, 'commit2'))
+        response.mustcontain(_commit_div(cs2.raw_id, 'commit3'))
 
         response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
         response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
         ## files
-        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=x#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
+        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=1#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
         #swap
-        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
+        response.mustcontain("""<a class="btn btn-small" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-refresh"></i> Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
+
+    def test_compare_forks_on_branch_extra_commits_origin_has_incomming_git(self):
+        self.log_user()
+
+        repo1 = fixture.create_repo('one-git', repo_type='git',
+                                    repo_description='diff-test',
+                                    cur_user=TEST_USER_ADMIN_LOGIN)
+
+        self.r1_id = repo1.repo_id
+
+        #commit something !
+        cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
+                             message='commit1', vcs_type='git', parent=None, newfile=True)
+
+        #fork this repo
+        repo2 = fixture.create_fork('one-git', 'one-git-fork')
+        self.r2_id = repo2.repo_id
+
+        #now commit something to origin repo
+        cs1_prim = _commit_change(repo1.repo_name, filename='file2', content='line1file2\n',
+                                  message='commit2', vcs_type='git', parent=cs0, newfile=True)
+
+        #add two extra commit into fork
+        cs1 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\n',
+                             message='commit2', vcs_type='git', parent=cs0)
+
+        cs2 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
+                             message='commit3', vcs_type='git', parent=cs1)
+
+        rev1 = 'master'
+        rev2 = 'master'
+
+        response = self.app.get(url('compare_url',
+                                    repo_name=repo1.repo_name,
+                                    org_ref_type="branch",
+                                    org_ref=rev2,
+                                    other_repo=repo2.repo_name,
+                                    other_ref_type="branch",
+                                    other_ref=rev1,
+                                    merge='1',))
+
+        response.mustcontain('%s@%s' % (repo1.repo_name, rev2))
+        response.mustcontain('%s@%s' % (repo2.repo_name, rev1))
+        response.mustcontain("""Showing 2 commits""")
+        response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
+
+        response.mustcontain(_commit_div(cs1.raw_id, 'commit2'))
+        response.mustcontain(_commit_div(cs2.raw_id, 'commit3'))
+
+        response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
+        response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
+        ## files
+        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=1#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
+        #swap
+        response.mustcontain("""<a class="btn btn-small" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-refresh"></i> Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
 
     def test_compare_cherry_pick_changesets_from_bottom(self):
 
@@ -195,7 +304,7 @@
         cs5 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\nline6\n',
                              message='commit6', vcs_type='hg', parent=cs4)
 
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=repo2.repo_name,
                                     org_ref_type="rev",
                                     org_ref=cs1.short_id,  # parent of cs2, in repo2
@@ -204,13 +313,14 @@
                                     other_ref=cs4.short_id,
                                     merge='True',
                                     ))
-        response.mustcontain('%s@%s -&gt; %s@%s' % (repo2.repo_name, cs1.short_id, repo1.repo_name, cs4.short_id))
+        response.mustcontain('%s@%s' % (repo2.repo_name, cs1.short_id))
+        response.mustcontain('%s@%s' % (repo1.repo_name, cs4.short_id))
         response.mustcontain("""Showing 3 commits""")
         response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
 
-        response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
-        response.mustcontain("""<div class="message tooltip" title="commit4" style="white-space:normal">commit4</div>""")
-        response.mustcontain("""<div class="message tooltip" title="commit5" style="white-space:normal">commit5</div>""")
+        response.mustcontain(_commit_div(cs2.raw_id, 'commit3'))
+        response.mustcontain(_commit_div(cs3.raw_id, 'commit4'))
+        response.mustcontain(_commit_div(cs4.raw_id, 'commit5'))
 
         response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo1.repo_name, cs2.raw_id, cs2.short_id))
         response.mustcontain("""<a href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
@@ -255,22 +365,23 @@
                              message='commit5', vcs_type='hg', parent=cs3)
         cs5 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\nline6\n',
                              message='commit6', vcs_type='hg', parent=cs4)
-        response = self.app.get(url(controller='compare', action='index',
+
+        response = self.app.get(url('compare_url',
                                     repo_name=repo1.repo_name,
                                     org_ref_type="rev",
                                     org_ref=cs2.short_id, # parent of cs3, not in repo2
                                     other_ref_type="rev",
                                     other_ref=cs5.short_id,
-                                    merge='1',
-                                    ))
+                                    merge='1',))
 
-        response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, cs2.short_id, repo1.repo_name, cs5.short_id))
+        response.mustcontain('%s@%s' % (repo1.repo_name, cs2.short_id))
+        response.mustcontain('%s@%s' % (repo1.repo_name, cs5.short_id))
         response.mustcontain("""Showing 3 commits""")
         response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
 
-        response.mustcontain("""<div class="message tooltip" title="commit4" style="white-space:normal">commit4</div>""")
-        response.mustcontain("""<div class="message tooltip" title="commit5" style="white-space:normal">commit5</div>""")
-        response.mustcontain("""<div class="message tooltip" title="commit6" style="white-space:normal">commit6</div>""")
+        response.mustcontain(_commit_div(cs3.raw_id, 'commit4'))
+        response.mustcontain(_commit_div(cs4.raw_id, 'commit5'))
+        response.mustcontain(_commit_div(cs5.raw_id, 'commit6'))
 
         response.mustcontain("""<a href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
         response.mustcontain("""<a href="/%s/changeset/%s">r4:%s</a>""" % (repo1.repo_name, cs4.raw_id, cs4.short_id))
@@ -279,8 +390,8 @@
         response.mustcontain("""#C--826e8142e6ba">file1</a>""")
 
     def test_compare_cherry_pick_changeset_mixed_branches(self):
-        pass
-        #TODO write this tastecase
+        #TODO: write this
+        assert 1
 
     def test_compare_remote_branches_hg(self):
         self.log_user()
@@ -290,16 +401,17 @@
         rev1 = '56349e29c2af'
         rev2 = '7d4bc8ec6be5'
 
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=HG_REPO,
                                     org_ref_type="rev",
                                     org_ref=rev1,
                                     other_ref_type="rev",
                                     other_ref=rev2,
                                     other_repo=HG_FORK,
-                                    merge='1',
-                                    ))
-        response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
+                                    merge='1',))
+
+        response.mustcontain('%s@%s' % (HG_REPO, rev1))
+        response.mustcontain('%s@%s' % (HG_FORK, rev2))
         ## outgoing changesets between those revisions
 
         response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_FORK))
@@ -311,7 +423,37 @@
         response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
         response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
 
-    def test_org_repo_new_commits_after_forking_simple_diff(self):
+    def test_compare_remote_branches_git(self):
+        self.log_user()
+
+        repo2 = fixture.create_fork(GIT_REPO, GIT_FORK)
+        self.r2_id = repo2.repo_id
+        rev1 = '102607b09cdd60e2793929c4f90478be29f85a17'
+        rev2 = 'd7e0d30fbcae12c90680eb095a4f5f02505ce501'
+
+        response = self.app.get(url('compare_url',
+                                    repo_name=GIT_REPO,
+                                    org_ref_type="rev",
+                                    org_ref=rev1,
+                                    other_ref_type="rev",
+                                    other_ref=rev2,
+                                    other_repo=GIT_FORK,
+                                    merge='1',))
+
+        response.mustcontain('%s@%s' % (GIT_REPO, rev1))
+        response.mustcontain('%s@%s' % (GIT_FORK, rev2))
+        ## outgoing changesets between those revisions
+
+        response.mustcontain("""<a href="/%s/changeset/49d3fd156b6f7db46313fac355dca1a0b94a0017">r4:49d3fd156b6f</a>""" % (GIT_FORK))
+        response.mustcontain("""<a href="/%s/changeset/2d1028c054665b962fa3d307adfc923ddd528038">r5:2d1028c05466</a>""" % (GIT_FORK))
+        response.mustcontain("""<a href="/%s/changeset/d7e0d30fbcae12c90680eb095a4f5f02505ce501">r6:%s</a>""" % (GIT_FORK, rev2[:12]))
+
+        ## files
+        response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (GIT_REPO, rev1, rev2, GIT_FORK))
+        response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (GIT_REPO, rev1, rev2, GIT_FORK))
+        response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--2f574d260608">vcs/backends/base.py</a>""" % (GIT_REPO, rev1, rev2, GIT_FORK))
+
+    def test_org_repo_new_commits_after_forking_simple_diff_hg(self):
         self.log_user()
 
         repo1 = fixture.create_repo('one', repo_type='hg',
@@ -355,28 +497,20 @@
         rev1 = 'default'
         rev2 = 'default'
 
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=r2_name,
                                     org_ref_type="branch",
                                     org_ref=rev1,
                                     other_ref_type="branch",
                                     other_ref=rev2,
                                     other_repo=r1_name,
-                                    merge='1',
-                                    ))
-        response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
+                                    merge='1',))
+
+        response.mustcontain('%s@%s' % (r2_name, rev1))
+        response.mustcontain('%s@%s' % (r1_name, rev2))
         response.mustcontain('No files')
         response.mustcontain('No changesets')
 
-        #add new commit into parent !
-#         cs0 = ScmModel().create_node(
-#             repo=repo1.scm_instance, repo_name=r1_name,
-#             cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
-#             author=TEST_USER_ADMIN_LOGIN,
-#             message='commit2-parent',
-#             content='line1-added-after-fork',
-#             f_path='file2'
-#         )
         cs0 = _commit_change(repo=r1_name, filename='file2',
                     content='line1-added-after-fork', message='commit2-parent',
                     vcs_type='hg', parent=None, newfile=True)
@@ -384,7 +518,7 @@
         #compare !
         rev1 = 'default'
         rev2 = 'default'
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=r2_name,
                                     org_ref_type="branch",
                                     org_ref=rev1,
@@ -394,8 +528,91 @@
                                     merge='1',
                                     ))
 
-        response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
+        response.mustcontain('%s@%s' % (r2_name, rev1))
+        response.mustcontain('%s@%s' % (r1_name, rev2))
 
         response.mustcontain("""commit2-parent""")
         response.mustcontain("""1 file changed with 1 insertions and 0 deletions""")
         response.mustcontain("""line1-added-after-fork""")
+
+    def test_org_repo_new_commits_after_forking_simple_diff_git(self):
+        self.log_user()
+
+        repo1 = fixture.create_repo('one-git', repo_type='git',
+                                    repo_description='diff-test',
+                                    cur_user=TEST_USER_ADMIN_LOGIN)
+
+        self.r1_id = repo1.repo_id
+        r1_name = repo1.repo_name
+
+        cs0 = _commit_change(repo=r1_name, filename='file1',
+                       content='line1', message='commit1', vcs_type='git',
+                       newfile=True)
+        Session().commit()
+        self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
+        #fork the repo1
+        repo2 = fixture.create_repo('one-git-fork', repo_type='git',
+                                    repo_description='diff-test',
+                                    cur_user=TEST_USER_ADMIN_LOGIN,
+                                    clone_uri=repo1.repo_full_path,
+                                    fork_of='one-git')
+        Session().commit()
+        self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
+        self.r2_id = repo2.repo_id
+        r2_name = repo2.repo_name
+
+
+        cs1 = _commit_change(repo=r2_name, filename='file1-fork',
+                       content='file1-line1-from-fork', message='commit1-fork',
+                       vcs_type='git', parent=repo2.scm_instance[-1],
+                       newfile=True)
+
+        cs2 = _commit_change(repo=r2_name, filename='file2-fork',
+                       content='file2-line1-from-fork', message='commit2-fork',
+                       vcs_type='git', parent=cs1,
+                       newfile=True)
+
+        cs3 = _commit_change(repo=r2_name, filename='file3-fork',
+                       content='file3-line1-from-fork', message='commit3-fork',
+                       vcs_type='git', parent=cs2, newfile=True)
+        #compare !
+        rev1 = 'master'
+        rev2 = 'master'
+
+        response = self.app.get(url('compare_url',
+                                    repo_name=r2_name,
+                                    org_ref_type="branch",
+                                    org_ref=rev1,
+                                    other_ref_type="branch",
+                                    other_ref=rev2,
+                                    other_repo=r1_name,
+                                    merge='1',))
+
+        response.mustcontain('%s@%s' % (r2_name, rev1))
+        response.mustcontain('%s@%s' % (r1_name, rev2))
+        response.mustcontain('No files')
+        response.mustcontain('No changesets')
+
+        cs0 = _commit_change(repo=r1_name, filename='file2',
+                    content='line1-added-after-fork', message='commit2-parent',
+                    vcs_type='git', parent=None, newfile=True)
+
+        #compare !
+        rev1 = 'master'
+        rev2 = 'master'
+        response = self.app.get(url('compare_url',
+                                    repo_name=r2_name,
+                                    org_ref_type="branch",
+                                    org_ref=rev1,
+                                    other_ref_type="branch",
+                                    other_ref=rev2,
+                                    other_repo=r1_name,
+                                    merge='1',
+                                    ))
+
+        response.mustcontain('%s@%s' % (r2_name, rev1))
+        response.mustcontain('%s@%s' % (r1_name, rev2))
+
+        response.mustcontain("""commit2-parent""")
+        response.mustcontain("""1 file changed with 1 insertions and 0 deletions""")
+        response.mustcontain("""line1-added-after-fork""")
--- a/rhodecode/tests/functional/test_compare_local.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_compare_local.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,10 +1,5 @@
+# -*- coding: utf-8 -*-
 from rhodecode.tests import *
-from rhodecode.model.repo import RepoModel
-from rhodecode.model.meta import Session
-from rhodecode.model.db import Repository
-from rhodecode.model.scm import ScmModel
-from rhodecode.lib.vcs.backends.base import EmptyChangeset
-
 
 class TestCompareController(TestController):
 
@@ -12,14 +7,15 @@
         self.log_user()
         tag1 = 'v0.1.2'
         tag2 = 'v0.1.3'
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=HG_REPO,
                                     org_ref_type="tag",
                                     org_ref=tag1,
                                     other_ref_type="tag",
                                     other_ref=tag2,
                                     ), status=200)
-        response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
+        response.mustcontain('%s@%s' % (HG_REPO, tag1))
+        response.mustcontain('%s@%s' % (HG_REPO, tag2))
 
         ## outgoing changesets between tags
         response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
@@ -49,14 +45,15 @@
         self.log_user()
         tag1 = 'v0.1.2'
         tag2 = 'v0.1.3'
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=GIT_REPO,
                                     org_ref_type="tag",
                                     org_ref=tag1,
                                     other_ref_type="tag",
                                     other_ref=tag2,
                                     ), status=200)
-        response.mustcontain('%s@%s -&gt; %s@%s' % (GIT_REPO, tag1, GIT_REPO, tag2))
+        response.mustcontain('%s@%s' % (GIT_REPO, tag1))
+        response.mustcontain('%s@%s' % (GIT_REPO, tag2))
 
         ## outgoing changesets between tags
         response.mustcontain('''<a href="/%s/changeset/794bbdd31545c199f74912709ea350dedcd189a2">r113:794bbdd31545</a>''' % GIT_REPO)
@@ -84,7 +81,7 @@
 
     def test_index_branch_hg(self):
         self.log_user()
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=HG_REPO,
                                     org_ref_type="branch",
                                     org_ref='default',
@@ -92,14 +89,15 @@
                                     other_ref='default',
                                     ))
 
-        response.mustcontain('%s@default -&gt; %s@default' % (HG_REPO, HG_REPO))
+        response.mustcontain('%s@default' % (HG_REPO))
+        response.mustcontain('%s@default' % (HG_REPO))
         # branch are equal
         response.mustcontain('<span class="empty_data">No files</span>')
         response.mustcontain('<span class="empty_data">No changesets</span>')
 
     def test_index_branch_git(self):
         self.log_user()
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=GIT_REPO,
                                     org_ref_type="branch",
                                     org_ref='master',
@@ -107,7 +105,8 @@
                                     other_ref='master',
                                     ))
 
-        response.mustcontain('%s@master -&gt; %s@master' % (GIT_REPO, GIT_REPO))
+        response.mustcontain('%s@master' % (GIT_REPO))
+        response.mustcontain('%s@master' % (GIT_REPO))
         # branch are equal
         response.mustcontain('<span class="empty_data">No files</span>')
         response.mustcontain('<span class="empty_data">No changesets</span>')
@@ -117,14 +116,16 @@
         rev1 = 'b986218ba1c9'
         rev2 = '3d8f361e72ab'
 
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=HG_REPO,
                                     org_ref_type="rev",
                                     org_ref=rev1,
                                     other_ref_type="rev",
                                     other_ref=rev2,
                                     ))
-        response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
+        response.mustcontain('%s@%s' % (HG_REPO, rev1))
+        response.mustcontain('%s@%s' % (HG_REPO, rev2))
+
         ## outgoing changesets between those revisions
         response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2))
 
@@ -137,14 +138,16 @@
         rev1 = 'c1214f7e79e02fc37156ff215cd71275450cffc3'
         rev2 = '38b5fe81f109cb111f549bfe9bb6b267e10bc557'
 
-        response = self.app.get(url(controller='compare', action='index',
+        response = self.app.get(url('compare_url',
                                     repo_name=GIT_REPO,
                                     org_ref_type="rev",
                                     org_ref=rev1,
                                     other_ref_type="rev",
                                     other_ref=rev2,
                                     ))
-        response.mustcontain('%s@%s -&gt; %s@%s' % (GIT_REPO, rev1, GIT_REPO, rev2))
+        response.mustcontain('%s@%s' % (GIT_REPO, rev1))
+        response.mustcontain('%s@%s' % (GIT_REPO, rev2))
+
         ## outgoing changesets between those revisions
         response.mustcontain("""<a href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12]))
         response.mustcontain('1 file changed with 7 insertions and 0 deletions')
--- a/rhodecode/tests/functional/test_files.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_files.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 import os
 from rhodecode.tests import *
 from rhodecode.model.db import Repository
@@ -12,6 +13,9 @@
     '.zip': ('application/zip', 'zip', ''),
 }
 
+HG_NODE_HISTORY = fixture.load_resource('hg_node_history_response.json')
+GIT_NODE_HISTORY = fixture.load_resource('git_node_history_response.json')
+
 
 def _set_downloads(repo_name, set_to):
     repo = Repository.get_by_repo_name(repo_name)
@@ -107,207 +111,29 @@
         self.log_user()
         response = self.app.get(url(controller='files', action='history',
                                     repo_name=HG_REPO,
-                                    revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
+                                    revision='tip',
                                     f_path='vcs/nodes.py'),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},)
-        #test or history
-        response.mustcontain("""<optgroup label="Changesets">
-<option value="dbec37a0d5cab8ff39af4cfc4a4cd3996e4acfc6">r648:dbec37a0d5ca (default)</option>
-<option value="1d20ed9eda9482d46ff0a6af5812550218b3ff15">r639:1d20ed9eda94 (default)</option>
-<option value="0173395e822797f098799ed95c1a81b6a547a9ad">r547:0173395e8227 (default)</option>
-<option value="afbb45ade933a8182f1d8ec5d4d1bb2de2572043">r546:afbb45ade933 (default)</option>
-<option value="6f093e30cac34e6b4b11275a9f22f80c5d7ad1f7">r502:6f093e30cac3 (default)</option>
-<option value="c7e2212dd2ae975d1d06534a3d7e317165c06960">r476:c7e2212dd2ae (default)</option>
-<option value="45477506df79f701bf69419aac3e1f0fed3c5bcf">r472:45477506df79 (default)</option>
-<option value="5fc76cb25d11e07c60de040f78b8cd265ff10d53">r469:5fc76cb25d11 (default)</option>
-<option value="b073433cf8994969ee5cd7cce84cbe587bb880b2">r468:b073433cf899 (default)</option>
-<option value="7a74dbfcacd1dbcb58bb9c860b2f29fbb22c4c96">r467:7a74dbfcacd1 (default)</option>
-<option value="71ee52cc4d629096bdbee036325975dac2af4501">r465:71ee52cc4d62 (default)</option>
-<option value="a5b217d26c5f111e72bae4de672b084ee0fbf75c">r452:a5b217d26c5f (default)</option>
-<option value="47aedd538bf616eedcb0e7d630ea476df0e159c7">r450:47aedd538bf6 (default)</option>
-<option value="8e4915fa32d727dcbf09746f637a5f82e539511e">r432:8e4915fa32d7 (default)</option>
-<option value="25213a5fbb048dff8ba65d21e466a835536e5b70">r356:25213a5fbb04 (default)</option>
-<option value="23debcedddc1c23c14be33e713e7786d4a9de471">r351:23debcedddc1 (default)</option>
-<option value="61e25b2a90a19e7fffd75dea1e4c7e20df526bbe">r342:61e25b2a90a1 (default)</option>
-<option value="fb95b340e0d03fa51f33c56c991c08077c99303e">r318:fb95b340e0d0 (webvcs)</option>
-<option value="bda35e0e564fbbc5cd26fe0a37fb647a254c99fe">r303:bda35e0e564f (default)</option>
-<option value="97ff74896d7dbf3115a337a421d44b55154acc89">r302:97ff74896d7d (default)</option>
-<option value="cec3473c3fdb9599c98067182a075b49bde570f9">r293:cec3473c3fdb (default)</option>
-<option value="0e86c43eef866a013a587666a877c879899599bb">r289:0e86c43eef86 (default)</option>
-<option value="91a27c312808100cf20a602f78befbbff9d89bfd">r288:91a27c312808 (default)</option>
-<option value="400e36a1670a57d11e3edcb5b07bf82c30006d0b">r287:400e36a1670a (default)</option>
-<option value="014fb17dfc95b0995e838c565376bf9a993e230a">r261:014fb17dfc95 (default)</option>
-<option value="cca7aebbc4d6125798446b11e69dc8847834a982">r260:cca7aebbc4d6 (default)</option>
-<option value="14cdb2957c011a5feba36f50d960d9832ba0f0c1">r258:14cdb2957c01 (workdir)</option>
-<option value="34df20118ed74b5987d22a579e8a60e903da5bf8">r245:34df20118ed7 (default)</option>
-<option value="0375d9042a64a1ac1641528f0f0668f9a339e86d">r233:0375d9042a64 (workdir)</option>
-<option value="94aa45fc1806c04d4ba640933edf682c22478453">r222:94aa45fc1806 (workdir)</option>
-<option value="7ed99bc738818879941e3ce20243f8856a7cfc84">r188:7ed99bc73881 (default)</option>
-<option value="1e85975528bcebe853732a9e5fb8dbf4461f6bb2">r184:1e85975528bc (default)</option>
-<option value="ed30beddde7bbddb26042625be19bcd11576c1dd">r183:ed30beddde7b (default)</option>
-<option value="a6664e18181c6fc81b751a8d01474e7e1a3fe7fc">r177:a6664e18181c (default)</option>
-<option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
-<option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
-<option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
-<option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
-<option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
-<option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
-<option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
-<option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
-<option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
-<option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
-<option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
-<option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
-<option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
-<option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
-<option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
-<option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
-<option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
-<option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
-<option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
-<option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
-<option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
-<option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
-<option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
-<option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
-<option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
-<option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
-<option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
-<option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
-<option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
-<option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
-<option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
-<option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
-<option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
-</optgroup>
-<optgroup label="Branches">
-<option value="96507bd11ecc815ebc6270fdf6db110928c09c1e">default</option>
-<option value="4f7e2131323e0749a740c0a56ab68ae9269c562a">stable</option>
-</optgroup>
-<optgroup label="Tags">
-<option value="2c96c02def9a7c997f33047761a53943e6254396">v0.2.0</option>
-<option value="8680b1d1cee3aa3c1ab3734b76ee164bbedbc5c9">v0.1.9</option>
-<option value="ecb25ba9c96faf1e65a0bc3fd914918420a2f116">v0.1.8</option>
-<option value="f67633a2894edaf28513706d558205fa93df9209">v0.1.7</option>
-<option value="02b38c0eb6f982174750c0e309ff9faddc0c7e12">v0.1.6</option>
-<option value="a6664e18181c6fc81b751a8d01474e7e1a3fe7fc">v0.1.5</option>
-<option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">v0.1.4</option>
-<option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">v0.1.3</option>
-<option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">v0.1.2</option>
-<option value="fef5bfe1dc17611d5fb59a7f6f95c55c3606f933">v0.1.11</option>
-<option value="92831aebf2f8dd4879e897024b89d09af214df1c">v0.1.10</option>
-<option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">v0.1.1</option>
-<option value="96507bd11ecc815ebc6270fdf6db110928c09c1e">tip</option>
-</optgroup>
-""")
+        self.assertEqual(response.body, HG_NODE_HISTORY)
+
+    def test_file_source_history_git(self):
+        self.log_user()
+        response = self.app.get(url(controller='files', action='history',
+                                    repo_name=GIT_REPO,
+                                    revision='master',
+                                    f_path='vcs/nodes.py'),
+                                extra_environ={'HTTP_X_PARTIAL_XHR': '1'},)
+        self.assertEqual(response.body, GIT_NODE_HISTORY)
 
     def test_file_annotation(self):
         self.log_user()
         response = self.app.get(url(controller='files', action='index',
                                     repo_name=HG_REPO,
-                                    revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
+                                    revision='tip',
                                     f_path='vcs/nodes.py',
                                     annotate=True))
 
-        response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">Branch: default</a></span>""")
-
-    def test_file_annotation_history(self):
-        self.log_user()
-        response = self.app.get(url(controller='files', action='history',
-                                    repo_name=HG_REPO,
-                                    revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
-                                    f_path='vcs/nodes.py',
-                                    annotate=True),
-                                extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
-
-        response.mustcontain("""
-<option value="dbec37a0d5cab8ff39af4cfc4a4cd3996e4acfc6">r648:dbec37a0d5ca (default)</option>
-<option value="1d20ed9eda9482d46ff0a6af5812550218b3ff15">r639:1d20ed9eda94 (default)</option>
-<option value="0173395e822797f098799ed95c1a81b6a547a9ad">r547:0173395e8227 (default)</option>
-<option value="afbb45ade933a8182f1d8ec5d4d1bb2de2572043">r546:afbb45ade933 (default)</option>
-<option value="6f093e30cac34e6b4b11275a9f22f80c5d7ad1f7">r502:6f093e30cac3 (default)</option>
-<option value="c7e2212dd2ae975d1d06534a3d7e317165c06960">r476:c7e2212dd2ae (default)</option>
-<option value="45477506df79f701bf69419aac3e1f0fed3c5bcf">r472:45477506df79 (default)</option>
-<option value="5fc76cb25d11e07c60de040f78b8cd265ff10d53">r469:5fc76cb25d11 (default)</option>
-<option value="b073433cf8994969ee5cd7cce84cbe587bb880b2">r468:b073433cf899 (default)</option>
-<option value="7a74dbfcacd1dbcb58bb9c860b2f29fbb22c4c96">r467:7a74dbfcacd1 (default)</option>
-<option value="71ee52cc4d629096bdbee036325975dac2af4501">r465:71ee52cc4d62 (default)</option>
-<option value="a5b217d26c5f111e72bae4de672b084ee0fbf75c">r452:a5b217d26c5f (default)</option>
-<option value="47aedd538bf616eedcb0e7d630ea476df0e159c7">r450:47aedd538bf6 (default)</option>
-<option value="8e4915fa32d727dcbf09746f637a5f82e539511e">r432:8e4915fa32d7 (default)</option>
-<option value="25213a5fbb048dff8ba65d21e466a835536e5b70">r356:25213a5fbb04 (default)</option>
-<option value="23debcedddc1c23c14be33e713e7786d4a9de471">r351:23debcedddc1 (default)</option>
-<option value="61e25b2a90a19e7fffd75dea1e4c7e20df526bbe">r342:61e25b2a90a1 (default)</option>
-<option value="fb95b340e0d03fa51f33c56c991c08077c99303e">r318:fb95b340e0d0 (webvcs)</option>
-<option value="bda35e0e564fbbc5cd26fe0a37fb647a254c99fe">r303:bda35e0e564f (default)</option>
-<option value="97ff74896d7dbf3115a337a421d44b55154acc89">r302:97ff74896d7d (default)</option>
-<option value="cec3473c3fdb9599c98067182a075b49bde570f9">r293:cec3473c3fdb (default)</option>
-<option value="0e86c43eef866a013a587666a877c879899599bb">r289:0e86c43eef86 (default)</option>
-<option value="91a27c312808100cf20a602f78befbbff9d89bfd">r288:91a27c312808 (default)</option>
-<option value="400e36a1670a57d11e3edcb5b07bf82c30006d0b">r287:400e36a1670a (default)</option>
-<option value="014fb17dfc95b0995e838c565376bf9a993e230a">r261:014fb17dfc95 (default)</option>
-<option value="cca7aebbc4d6125798446b11e69dc8847834a982">r260:cca7aebbc4d6 (default)</option>
-<option value="14cdb2957c011a5feba36f50d960d9832ba0f0c1">r258:14cdb2957c01 (workdir)</option>
-<option value="34df20118ed74b5987d22a579e8a60e903da5bf8">r245:34df20118ed7 (default)</option>
-<option value="0375d9042a64a1ac1641528f0f0668f9a339e86d">r233:0375d9042a64 (workdir)</option>
-<option value="94aa45fc1806c04d4ba640933edf682c22478453">r222:94aa45fc1806 (workdir)</option>
-<option value="7ed99bc738818879941e3ce20243f8856a7cfc84">r188:7ed99bc73881 (default)</option>
-<option value="1e85975528bcebe853732a9e5fb8dbf4461f6bb2">r184:1e85975528bc (default)</option>
-<option value="ed30beddde7bbddb26042625be19bcd11576c1dd">r183:ed30beddde7b (default)</option>
-<option value="a6664e18181c6fc81b751a8d01474e7e1a3fe7fc">r177:a6664e18181c (default)</option>
-<option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
-<option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
-<option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
-<option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
-<option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
-<option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
-<option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
-<option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
-<option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
-<option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
-<option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
-<option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
-<option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
-<option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
-<option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
-<option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
-<option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
-<option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
-<option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
-<option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
-<option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
-<option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
-<option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
-<option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
-<option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
-<option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
-<option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
-<option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
-<option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
-<option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
-<option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
-<option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
-<option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
-</optgroup>
-<optgroup label="Branches">
-<option value="96507bd11ecc815ebc6270fdf6db110928c09c1e">default</option>
-<option value="4f7e2131323e0749a740c0a56ab68ae9269c562a">stable</option>
-</optgroup>
-<optgroup label="Tags">
-<option value="2c96c02def9a7c997f33047761a53943e6254396">v0.2.0</option>
-<option value="8680b1d1cee3aa3c1ab3734b76ee164bbedbc5c9">v0.1.9</option>
-<option value="ecb25ba9c96faf1e65a0bc3fd914918420a2f116">v0.1.8</option>
-<option value="f67633a2894edaf28513706d558205fa93df9209">v0.1.7</option>
-<option value="02b38c0eb6f982174750c0e309ff9faddc0c7e12">v0.1.6</option>
-<option value="a6664e18181c6fc81b751a8d01474e7e1a3fe7fc">v0.1.5</option>
-<option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">v0.1.4</option>
-<option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">v0.1.3</option>
-<option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">v0.1.2</option>
-<option value="fef5bfe1dc17611d5fb59a7f6f95c55c3606f933">v0.1.11</option>
-<option value="92831aebf2f8dd4879e897024b89d09af214df1c">v0.1.10</option>
-<option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">v0.1.1</option>
-<option value="96507bd11ecc815ebc6270fdf6db110928c09c1e">tip</option>
-</optgroup>""")
+        response.mustcontain("""r356:25213a5fbb04""")
 
     def test_file_annotation_git(self):
         self.log_user()
@@ -316,6 +142,49 @@
                                     revision='master',
                                     f_path='vcs/nodes.py',
                                     annotate=True))
+        response.mustcontain("""r345:c994f0de03b2""")
+
+    def test_file_annotation_history(self):
+        self.log_user()
+        response = self.app.get(url(controller='files', action='history',
+                                    repo_name=HG_REPO,
+                                    revision='tip',
+                                    f_path='vcs/nodes.py',
+                                    annotate=True),
+                                extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+
+        self.assertEqual(response.body, HG_NODE_HISTORY)
+
+    def test_file_annotation_history_git(self):
+        self.log_user()
+        response = self.app.get(url(controller='files', action='history',
+                                    repo_name=GIT_REPO,
+                                    revision='master',
+                                    f_path='vcs/nodes.py',
+                                    annotate=True),
+                                extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+
+        self.assertEqual(response.body, GIT_NODE_HISTORY)
+
+    def test_file_authors(self):
+        self.log_user()
+        response = self.app.get(url(controller='files', action='authors',
+                                    repo_name=HG_REPO,
+                                    revision='tip',
+                                    f_path='vcs/nodes.py',
+                                    annotate=True))
+        response.mustcontain('Marcin Kuzminski')
+        response.mustcontain('Lukasz Balcerzak')
+
+    def test_file_authors_git(self):
+        self.log_user()
+        response = self.app.get(url(controller='files', action='authors',
+                                    repo_name=GIT_REPO,
+                                    revision='master',
+                                    f_path='vcs/nodes.py',
+                                    annotate=True))
+        response.mustcontain('Marcin Kuzminski')
+        response.mustcontain('Lukasz Balcerzak')
 
     def test_archival(self):
         self.log_user()
@@ -385,7 +254,7 @@
                                     revision=rev,
                                     f_path=f_path), status=404)
 
-        msg = """Revision %s does not exist for this repository""" % (rev)
+        msg = """Such revision does not exist for this repository"""
         response.mustcontain(msg)
 
     def test_raw_file_wrong_f_path(self):
@@ -422,7 +291,7 @@
                                     revision=rev,
                                     f_path=f_path), status=404)
 
-        msg = """Revision %s does not exist for this repository""" % (rev)
+        msg = """Such revision does not exist for this repository"""
         response.mustcontain(msg)
 
     def test_raw_wrong_f_path(self):
@@ -732,3 +601,143 @@
                                    'Successfully committed to vcs/nodes.py')
         finally:
             fixture.destroy_repo(repo.repo_name)
+
+    # HG - delete
+    def test_delete_file_view_hg(self):
+        self.log_user()
+        response = self.app.get(url('files_delete_home',
+                                     repo_name=HG_REPO,
+                                     revision='tip', f_path='vcs/nodes.py'))
+
+    def test_delete_file_view_not_on_branch_hg(self):
+        self.log_user()
+        repo = fixture.create_repo('test-delete-repo', repo_type='hg')
+
+        ## add file
+        location = 'vcs'
+        filename = 'nodes.py'
+        response = self.app.post(url('files_add_home',
+                                      repo_name=repo.repo_name,
+                                      revision='tip', f_path='/'),
+                                 params={
+                                    'content': "def py():\n print 'hello'\n",
+                                    'filename': filename,
+                                    'location': location
+                                 },
+                                 status=302)
+        response.follow()
+        try:
+            self.checkSessionFlash(response, 'Successfully committed to %s'
+                                   % os.path.join(location, filename))
+            response = self.app.get(url('files_delete_home',
+                                          repo_name=repo.repo_name,
+                                          revision='tip', f_path='vcs/nodes.py'),
+                                    status=302)
+            self.checkSessionFlash(response,
+                'You can only delete files with revision being a valid branch')
+        finally:
+            fixture.destroy_repo(repo.repo_name)
+
+    def test_delete_file_view_commit_changes_hg(self):
+        self.log_user()
+        repo = fixture.create_repo('test-delete-repo', repo_type='hg')
+
+        ## add file
+        location = 'vcs'
+        filename = 'nodes.py'
+        response = self.app.post(url('files_add_home',
+                                      repo_name=repo.repo_name,
+                                      revision='tip',
+                                      f_path='/'),
+                                 params={
+                                    'content': "def py():\n print 'hello'\n",
+                                    'filename': filename,
+                                    'location': location
+                                 },
+                                 status=302)
+        response.follow()
+        try:
+            self.checkSessionFlash(response, 'Successfully committed to %s'
+                                   % os.path.join(location, filename))
+            response = self.app.post(url('files_delete_home',
+                                          repo_name=repo.repo_name,
+                                          revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
+                                          f_path='vcs/nodes.py'),
+                                     params={
+                                        'message': 'i commited',
+                                     },
+                                    status=302)
+            self.checkSessionFlash(response,
+                                   'Successfully deleted file vcs/nodes.py')
+        finally:
+            fixture.destroy_repo(repo.repo_name)
+
+    # GIT - delete
+    def test_delete_file_view_git(self):
+        self.log_user()
+        response = self.app.get(url('files_delete_home',
+                                     repo_name=HG_REPO,
+                                     revision='tip', f_path='vcs/nodes.py'))
+
+    def test_delete_file_view_not_on_branch_git(self):
+        self.log_user()
+        repo = fixture.create_repo('test-delete-repo', repo_type='git')
+
+        ## add file
+        location = 'vcs'
+        filename = 'nodes.py'
+        response = self.app.post(url('files_add_home',
+                                      repo_name=repo.repo_name,
+                                      revision='tip', f_path='/'),
+                                 params={
+                                    'content': "def py():\n print 'hello'\n",
+                                    'filename': filename,
+                                    'location': location
+                                 },
+                                 status=302)
+        response.follow()
+        try:
+            self.checkSessionFlash(response, 'Successfully committed to %s'
+                                   % os.path.join(location, filename))
+            response = self.app.get(url('files_delete_home',
+                                          repo_name=repo.repo_name,
+                                          revision='tip', f_path='vcs/nodes.py'),
+                                    status=302)
+            self.checkSessionFlash(response,
+                'You can only delete files with revision being a valid branch')
+        finally:
+            fixture.destroy_repo(repo.repo_name)
+
+    def test_delete_file_view_commit_changes_git(self):
+        self.log_user()
+        repo = fixture.create_repo('test-delete-repo', repo_type='git')
+
+        ## add file
+        location = 'vcs'
+        filename = 'nodes.py'
+        response = self.app.post(url('files_add_home',
+                                      repo_name=repo.repo_name,
+                                      revision='tip',
+                                      f_path='/'),
+                                 params={
+                                    'content': "def py():\n print 'hello'\n",
+                                    'filename': filename,
+                                    'location': location
+                                 },
+                                 status=302)
+        response.follow()
+        try:
+            self.checkSessionFlash(response, 'Successfully committed to %s'
+                                   % os.path.join(location, filename))
+            response = self.app.post(url('files_delete_home',
+                                          repo_name=repo.repo_name,
+                                          revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
+                                          f_path='vcs/nodes.py'),
+                                     params={
+                                        'message': 'i commited',
+                                     },
+                                    status=302)
+            self.checkSessionFlash(response,
+                                   'Successfully deleted file vcs/nodes.py')
+        finally:
+            fixture.destroy_repo(repo.repo_name)
--- a/rhodecode/tests/functional/test_forks.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_forks.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,20 +1,39 @@
+# -*- coding: utf-8 -*-
 from rhodecode.tests import *
+from rhodecode.tests.fixture import Fixture
 
 from rhodecode.model.db import Repository
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.user import UserModel
 from rhodecode.model.meta import Session
 
+fixture = Fixture()
 
-class TestForksController(TestController):
+from rhodecode.tests import *
+
+
+class _BaseTest(TestController):
+    """
+    Write all tests here
+    """
+    REPO = None
+    REPO_TYPE = None
+    NEW_REPO = None
+    REPO_FORK = None
+
+    @classmethod
+    def setup_class(cls):
+        pass
+
+    @classmethod
+    def teardown_class(cls):
+        pass
 
     def setUp(self):
         self.username = u'forkuser'
         self.password = u'qweqwe'
-        self.u1 = UserModel().create_or_update(
-            username=self.username, password=self.password,
-            email=u'fork_king@rhodecode.org', firstname=u'u1', lastname=u'u1'
-        )
+        self.u1 = fixture.create_user(self.username, password=self.password,
+                                      email=u'fork_king@rhodecode.org')
         Session().commit()
 
     def tearDown(self):
@@ -23,7 +42,7 @@
 
     def test_index(self):
         self.log_user()
-        repo_name = HG_REPO
+        repo_name = self.REPO
         response = self.app.get(url(controller='forks', action='forks',
                                     repo_name=repo_name))
 
@@ -39,28 +58,29 @@
         u.inherit_default_permissions = False
         Session().commit()
         # try create a fork
-        repo_name = HG_REPO
+        repo_name = self.REPO
         self.app.post(url(controller='forks', action='fork_create',
                           repo_name=repo_name), {}, status=403)
 
-    def test_index_with_fork_hg(self):
+    def test_index_with_fork(self):
         self.log_user()
 
         # create a fork
-        fork_name = HG_FORK
+        fork_name = self.REPO_FORK
         description = 'fork of vcs test'
-        repo_name = HG_REPO
+        repo_name = self.REPO
         org_repo = Repository.get_by_repo_name(repo_name)
-        response = self.app.post(url(controller='forks',
-                                     action='fork_create',
-                                    repo_name=repo_name),
-                                    {'repo_name': fork_name,
-                                     'repo_group': '',
-                                     'fork_parent_id': org_repo.repo_id,
-                                     'repo_type': 'hg',
-                                     'description': description,
-                                     'private': 'False',
-                                     'landing_rev': 'tip'})
+        creation_args = {
+            'repo_name': fork_name,
+            'repo_group': '',
+            'fork_parent_id': org_repo.repo_id,
+            'repo_type': self.REPO_TYPE,
+            'description': description,
+            'private': 'False',
+            'landing_rev': 'rev:tip'}
+
+        self.app.post(url(controller='forks', action='fork_create',
+                          repo_name=repo_name), creation_args)
 
         response = self.app.get(url(controller='forks', action='forks',
                                     repo_name=repo_name))
@@ -69,54 +89,75 @@
             """<a href="/%s">%s</a>""" % (fork_name, fork_name)
         )
 
-        #remove this fork
+        # remove this fork
         response = self.app.delete(url('repo', repo_name=fork_name))
 
-    def test_index_with_fork_git(self):
+    def test_fork_create_into_group(self):
         self.log_user()
-
-        # create a fork
-        fork_name = GIT_FORK
+        group = fixture.create_repo_group('vc')
+        group_id = group.group_id
+        fork_name = self.REPO_FORK
+        fork_name_full = 'vc/%s' % fork_name
         description = 'fork of vcs test'
-        repo_name = GIT_REPO
+        repo_name = self.REPO
         org_repo = Repository.get_by_repo_name(repo_name)
-        response = self.app.post(url(controller='forks',
-                                     action='fork_create',
-                                    repo_name=repo_name),
-                                    {'repo_name': fork_name,
-                                     'repo_group': '',
-                                     'fork_parent_id': org_repo.repo_id,
-                                     'repo_type': 'git',
-                                     'description': description,
-                                     'private': 'False',
-                                     'landing_rev': 'tip'})
+        creation_args = {
+            'repo_name': fork_name,
+            'repo_group': group_id,
+            'fork_parent_id': org_repo.repo_id,
+            'repo_type': self.REPO_TYPE,
+            'description': description,
+            'private': 'False',
+            'landing_rev': 'rev:tip'}
+        self.app.post(url(controller='forks', action='fork_create',
+                          repo_name=repo_name), creation_args)
+        repo = Repository.get_by_repo_name(fork_name_full)
+        assert repo.fork.repo_name == self.REPO
 
-        response = self.app.get(url(controller='forks', action='forks',
-                                    repo_name=repo_name))
+        ## run the check page that triggers the flash message
+        response = self.app.get(url('repo_check_home', repo_name=fork_name_full))
+        #test if we have a message that fork is ok
+        self.checkSessionFlash(response,
+                'Forked repository %s as <a href="/%s">%s</a>'
+                % (repo_name, fork_name_full, fork_name_full))
+
+        #test if the fork was created in the database
+        fork_repo = Session().query(Repository)\
+            .filter(Repository.repo_name == fork_name_full).one()
 
-        response.mustcontain(
-            """<a href="/%s">%s</a>""" % (fork_name, fork_name)
-        )
+        self.assertEqual(fork_repo.repo_name, fork_name_full)
+        self.assertEqual(fork_repo.fork.repo_name, repo_name)
 
-        #remove this fork
-        response = self.app.delete(url('repo', repo_name=fork_name))
+        # test if the repository is visible in the list ?
+        response = self.app.get(url('summary_home', repo_name=fork_name_full))
+        response.mustcontain(fork_name_full)
+        response.mustcontain(self.REPO_TYPE)
+        response.mustcontain('Fork of "<a href="/%s">%s</a>"' % (repo_name, repo_name))
+
+        fixture.destroy_repo(fork_name_full)
+        fixture.destroy_repo_group(group_id)
 
     def test_z_fork_create(self):
         self.log_user()
-        fork_name = HG_FORK
+        fork_name = self.REPO_FORK
         description = 'fork of vcs test'
-        repo_name = HG_REPO
+        repo_name = self.REPO
         org_repo = Repository.get_by_repo_name(repo_name)
-        response = self.app.post(url(controller='forks', action='fork_create',
-                                    repo_name=repo_name),
-                                    {'repo_name': fork_name,
-                                     'repo_group':'',
-                                     'fork_parent_id':org_repo.repo_id,
-                                     'repo_type':'hg',
-                                     'description':description,
-                                     'private':'False',
-                                     'landing_rev': 'tip'})
+        creation_args = {
+            'repo_name': fork_name,
+            'repo_group': '',
+            'fork_parent_id': org_repo.repo_id,
+            'repo_type': self.REPO_TYPE,
+            'description': description,
+            'private': 'False',
+            'landing_rev': 'rev:tip'}
+        self.app.post(url(controller='forks', action='fork_create',
+                          repo_name=repo_name), creation_args)
+        repo = Repository.get_by_repo_name(self.REPO_FORK)
+        assert repo.fork.repo_name == self.REPO
 
+        ## run the check page that triggers the flash message
+        response = self.app.get(url('repo_check_home', repo_name=fork_name))
         #test if we have a message that fork is ok
         self.checkSessionFlash(response,
                 'Forked repository %s as <a href="/%s">%s</a>'
@@ -129,21 +170,19 @@
         self.assertEqual(fork_repo.repo_name, fork_name)
         self.assertEqual(fork_repo.fork.repo_name, repo_name)
 
-        #test if fork is visible in the list ?
-        response = response.follow()
-
-        response = self.app.get(url(controller='summary', action='index',
-                                    repo_name=fork_name))
-
-        response.mustcontain('Fork of %s' % repo_name)
+        # test if the repository is visible in the list ?
+        response = self.app.get(url('summary_home', repo_name=fork_name))
+        response.mustcontain(fork_name)
+        response.mustcontain(self.REPO_TYPE)
+        response.mustcontain('Fork of "<a href="/%s">%s</a>"' % (repo_name, repo_name))
 
     def test_zz_fork_permission_page(self):
         usr = self.log_user(self.username, self.password)['user_id']
-        repo_name = HG_REPO
+        repo_name = self.REPO
 
-        forks = Session().query(Repository)\
-            .filter(Repository.fork_id != None)\
-            .all()
+        forks = Repository.query()\
+            .filter(Repository.repo_type == self.REPO_TYPE)\
+            .filter(Repository.fork_id != None).all()
         self.assertEqual(1, len(forks))
 
         # set read permissions for this
@@ -159,11 +198,11 @@
 
     def test_zzz_fork_permission_page(self):
         usr = self.log_user(self.username, self.password)['user_id']
-        repo_name = HG_REPO
+        repo_name = self.REPO
 
-        forks = Session().query(Repository)\
-            .filter(Repository.fork_id != None)\
-            .all()
+        forks = Repository.query()\
+            .filter(Repository.repo_type == self.REPO_TYPE)\
+            .filter(Repository.fork_id != None).all()
         self.assertEqual(1, len(forks))
 
         # set none
@@ -174,3 +213,17 @@
         response = self.app.get(url(controller='forks', action='forks',
                                     repo_name=repo_name))
         response.mustcontain('There are no forks yet')
+
+
+class TestGIT(_BaseTest):
+    REPO = GIT_REPO
+    NEW_REPO = NEW_GIT_REPO
+    REPO_TYPE = 'git'
+    REPO_FORK = GIT_FORK
+
+
+class TestHG(_BaseTest):
+    REPO = HG_REPO
+    NEW_REPO = NEW_HG_REPO
+    REPO_TYPE = 'hg'
+    REPO_FORK = HG_FORK
--- a/rhodecode/tests/functional/test_home.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_home.py	Wed Jul 02 19:03:13 2014 -0400
@@ -4,7 +4,7 @@
 from rhodecode.model.meta import Session
 from rhodecode.model.db import User, Repository
 from rhodecode.model.repo import RepoModel
-from rhodecode.model.repos_group import ReposGroupModel
+from rhodecode.model.repo_group import RepoGroupModel
 
 
 fixture = Fixture()
@@ -16,18 +16,13 @@
         self.log_user()
         response = self.app.get(url(controller='home', action='index'))
         #if global permission is set
-        response.mustcontain('Add repository')
+        response.mustcontain('Add Repository')
         # html in javascript variable:
-        response.mustcontain("""var data = {"totalRecords": %s"""
-                             % len(Repository.getAll()))
+        response.mustcontain('var data = {"totalRecords": %s' % len(Repository.getAll()))
         response.mustcontain(r'href=\"/%s\"' % HG_REPO)
 
-        response.mustcontain(r"""<img class=\"icon\" title=\"Mercurial repository\" """
-                        r"""alt=\"Mercurial repository\" src=\"/images/icons/hg"""
-                        r"""icon.png\"/>""")
-        response.mustcontain(r"""<img class=\"icon\" title=\"Public repository\" """
-                        r"""alt=\"Public repository\" src=\"/images/icons/public_"""
-                        r"""repo.png\"/>""")
+        response.mustcontain(r'<i class=\"icon-git\"')
+        response.mustcontain(r'<i class=\"icon-unlock-alt\"')
 
         response.mustcontain("""fixes issue with having custom format for git-log""")
         response.mustcontain("""/%s/changeset/5f2c6ee195929b0be80749243c18121c9864a3b3""" % GIT_REPO)
@@ -36,48 +31,27 @@
         response.mustcontain("""/%s/changeset/96507bd11ecc815ebc6270fdf6db110928c09c1e""" % HG_REPO)
 
     def test_repo_summary_with_anonymous_access_disabled(self):
-        anon = User.get_default_user()
-        anon.active = False
-        Session().add(anon)
-        Session().commit()
-        time.sleep(1.5)  # must sleep for cache (1s to expire)
-        try:
+        with fixture.anon_access(False):
             response = self.app.get(url(controller='summary',
                                         action='index', repo_name=HG_REPO),
                                         status=302)
             assert 'login' in response.location
 
-        finally:
-            anon = User.get_default_user()
-            anon.active = True
-            Session().add(anon)
-            Session().commit()
-
     def test_index_with_anonymous_access_disabled(self):
-        anon = User.get_default_user()
-        anon.active = False
-        Session().add(anon)
-        Session().commit()
-        time.sleep(1.5)  # must sleep for cache (1s to expire)
-        try:
+        with fixture.anon_access(False):
             response = self.app.get(url(controller='home', action='index'),
                                     status=302)
             assert 'login' in response.location
-        finally:
-            anon = User.get_default_user()
-            anon.active = True
-            Session().add(anon)
-            Session().commit()
 
     def test_index_page_on_groups(self):
         self.log_user()
-        gr = fixture.create_group('gr1')
-        fixture.create_repo(name='gr1/repo_in_group', repos_group=gr)
+        gr = fixture.create_repo_group('gr1')
+        fixture.create_repo(name='gr1/repo_in_group', repo_group=gr)
         response = self.app.get(url('repos_group_home', group_name='gr1'))
 
         try:
             response.mustcontain("gr1/repo_in_group")
         finally:
             RepoModel().delete('gr1/repo_in_group')
-            ReposGroupModel().delete(repos_group='gr1', force_delete=True)
+            RepoGroupModel().delete(repo_group='gr1', force_delete=True)
             Session().commit()
--- a/rhodecode/tests/functional/test_login.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_login.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,12 +1,18 @@
 # -*- coding: utf-8 -*-
+from __future__ import with_statement
+import mock
 from rhodecode.tests import *
-from rhodecode.model.db import User, Notification
+from rhodecode.tests.fixture import Fixture
 from rhodecode.lib.utils2 import generate_api_key
 from rhodecode.lib.auth import check_password
 from rhodecode.lib import helpers as h
+from rhodecode.model.api_key import ApiKeyModel
 from rhodecode.model import validators
+from rhodecode.model.db import User, Notification
 from rhodecode.model.meta import Session
 
+fixture = Fixture()
+
 
 class TestLoginController(TestController):
 
@@ -95,7 +101,7 @@
     #==========================================================================
     def test_register(self):
         response = self.app.get(url(controller='login', action='register'))
-        response.mustcontain('Sign Up to RhodeCode')
+        response.mustcontain('Sign Up')
 
     def test_register_err_same_username(self):
         uname = 'test_admin'
@@ -289,3 +295,81 @@
                                 'new password has been sent to your email'))
 
         response = response.follow()
+
+    def _get_api_whitelist(self, values=None):
+        config = {'api_access_controllers_whitelist': values or []}
+        return config
+
+    @parameterized.expand([
+        ('none', None),
+        ('empty_string', ''),
+        ('fake_number', '123456'),
+        ('proper_api_key', None)
+    ])
+    def test_access_not_whitelisted_page_via_api_key(self, test_name, api_key):
+        whitelist = self._get_api_whitelist([])
+        with mock.patch('rhodecode.CONFIG', whitelist):
+            self.assertEqual([],
+                             whitelist['api_access_controllers_whitelist'])
+            if test_name == 'proper_api_key':
+                #use builtin if api_key is None
+                api_key = User.get_first_admin().api_key
+
+            with fixture.anon_access(False):
+                self.app.get(url(controller='changeset',
+                                 action='changeset_raw',
+                                 repo_name=HG_REPO, revision='tip', api_key=api_key),
+                             status=302)
+
+    @parameterized.expand([
+        ('none', None, 302),
+        ('empty_string', '', 302),
+        ('fake_number', '123456', 302),
+        ('proper_api_key', None, 200)
+    ])
+    def test_access_whitelisted_page_via_api_key(self, test_name, api_key, code):
+        whitelist = self._get_api_whitelist(['ChangesetController:changeset_raw'])
+        with mock.patch('rhodecode.CONFIG', whitelist):
+            self.assertEqual(['ChangesetController:changeset_raw'],
+                             whitelist['api_access_controllers_whitelist'])
+            if test_name == 'proper_api_key':
+                api_key = User.get_first_admin().api_key
+
+            with fixture.anon_access(False):
+                self.app.get(url(controller='changeset',
+                                 action='changeset_raw',
+                                 repo_name=HG_REPO, revision='tip', api_key=api_key),
+                             status=code)
+
+    def test_access_page_via_extra_api_key(self):
+        whitelist = self._get_api_whitelist(['ChangesetController:changeset_raw'])
+        with mock.patch('rhodecode.CONFIG', whitelist):
+            self.assertEqual(['ChangesetController:changeset_raw'],
+                             whitelist['api_access_controllers_whitelist'])
+
+            new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, 'test')
+            Session().commit()
+            with fixture.anon_access(False):
+                self.app.get(url(controller='changeset',
+                                 action='changeset_raw',
+                                 repo_name=HG_REPO, revision='tip', api_key=new_api_key.api_key),
+                             status=200)
+
+    def test_access_page_via_expired_api_key(self):
+        whitelist = self._get_api_whitelist(['ChangesetController:changeset_raw'])
+        with mock.patch('rhodecode.CONFIG', whitelist):
+            self.assertEqual(['ChangesetController:changeset_raw'],
+                             whitelist['api_access_controllers_whitelist'])
+
+            new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, 'test')
+            Session().commit()
+            #patch the api key and make it expired
+            new_api_key.expires = 0
+            Session().add(new_api_key)
+            Session().commit()
+            with fixture.anon_access(False):
+                self.app.get(url(controller='changeset',
+                                 action='changeset_raw',
+                                 repo_name=HG_REPO, revision='tip',
+                                 api_key=new_api_key.api_key),
+                             status=302)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/functional/test_my_account.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,248 @@
+# -*- coding: utf-8 -*-
+
+from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys
+from rhodecode.tests import *
+from rhodecode.tests.fixture import Fixture
+from rhodecode.lib import helpers as h
+from rhodecode.model.user import UserModel
+from rhodecode.model.meta import Session
+
+fixture = Fixture()
+
+
+class TestMyAccountController(TestController):
+    test_user_1 = 'testme'
+
+    @classmethod
+    def teardown_class(cls):
+        if User.get_by_username(cls.test_user_1):
+            UserModel().delete(cls.test_user_1)
+            Session().commit()
+
+    def test_my_account(self):
+        self.log_user()
+        response = self.app.get(url('my_account'))
+
+        response.mustcontain('value="test_admin')
+
+    def test_my_account_my_repos(self):
+        self.log_user()
+        response = self.app.get(url('my_account_repos'))
+        cnt = Repository.query().filter(Repository.user ==
+                           User.get_by_username(TEST_USER_ADMIN_LOGIN)).count()
+        response.mustcontain('"totalRecords": %s' % cnt)
+
+    def test_my_account_my_watched(self):
+        self.log_user()
+        response = self.app.get(url('my_account_watched'))
+
+        cnt = UserFollowing.query().filter(UserFollowing.user ==
+                            User.get_by_username(TEST_USER_ADMIN_LOGIN)).count()
+        response.mustcontain('"totalRecords": %s' % cnt)
+
+    def test_my_account_my_pullrequests(self):
+        self.log_user()
+        response = self.app.get(url('my_account_pullrequests'))
+
+        response.mustcontain('Nothing here yet')
+
+    def test_my_account_my_emails(self):
+        self.log_user()
+        response = self.app.get(url('my_account_emails'))
+        response.mustcontain('No additional emails specified')
+
+    def test_my_account_my_emails_add_existing_email(self):
+        self.log_user()
+        response = self.app.get(url('my_account_emails'))
+        response.mustcontain('No additional emails specified')
+        response = self.app.post(url('my_account_emails'),
+                                 {'new_email': TEST_USER_REGULAR_EMAIL})
+        self.checkSessionFlash(response, 'This e-mail address is already taken')
+
+    def test_my_account_my_emails_add_mising_email_in_form(self):
+        self.log_user()
+        response = self.app.get(url('my_account_emails'))
+        response.mustcontain('No additional emails specified')
+        response = self.app.post(url('my_account_emails'),)
+        self.checkSessionFlash(response, 'Please enter an email address')
+
+    def test_my_account_my_emails_add_remove(self):
+        self.log_user()
+        response = self.app.get(url('my_account_emails'))
+        response.mustcontain('No additional emails specified')
+
+        response = self.app.post(url('my_account_emails'),
+                                 {'new_email': 'foo@barz.com'})
+
+        response = self.app.get(url('my_account_emails'))
+
+        from rhodecode.model.db import UserEmailMap
+        email_id = UserEmailMap.query()\
+            .filter(UserEmailMap.user == User.get_by_username(TEST_USER_ADMIN_LOGIN))\
+            .filter(UserEmailMap.email == 'foo@barz.com').one().email_id
+
+        response.mustcontain('foo@barz.com')
+        response.mustcontain('<input id="del_email_id" name="del_email_id" type="hidden" value="%s" />' % email_id)
+
+        response = self.app.post(url('my_account_emails'),
+                                 {'del_email_id': email_id, '_method': 'delete'})
+        self.checkSessionFlash(response, 'Removed email from user')
+        response = self.app.get(url('my_account_emails'))
+        response.mustcontain('No additional emails specified')
+
+
+    @parameterized.expand(
+        [('firstname', {'firstname': 'new_username'}),
+         ('lastname', {'lastname': 'new_username'}),
+         ('admin', {'admin': True}),
+         ('admin', {'admin': False}),
+         ('extern_type', {'extern_type': 'ldap'}),
+         ('extern_type', {'extern_type': None}),
+         #('extern_name', {'extern_name': 'test'}),
+         #('extern_name', {'extern_name': None}),
+         ('active', {'active': False}),
+         ('active', {'active': True}),
+         ('email', {'email': 'some@email.com'}),
+        # ('new_password', {'new_password': 'foobar123',
+        #                   'password_confirmation': 'foobar123'})
+        ])
+    def test_my_account_update(self, name, attrs):
+        usr = fixture.create_user(self.test_user_1, password='qweqwe',
+                                  email='testme@rhodecode.org',
+                                  extern_type='rhodecode',
+                                  extern_name=self.test_user_1,
+                                  skip_if_exists=True)
+        params = usr.get_api_data()  # current user data
+        user_id = usr.user_id
+        self.log_user(username=self.test_user_1, password='qweqwe')
+
+        params.update({'password_confirmation': ''})
+        params.update({'new_password': ''})
+        params.update({'extern_type': 'rhodecode'})
+        params.update({'extern_name': self.test_user_1})
+
+        params.update(attrs)
+        response = self.app.post(url('my_account'), params)
+
+        self.checkSessionFlash(response,
+                               'Your account was updated successfully')
+
+        updated_user = User.get_by_username(self.test_user_1)
+        updated_params = updated_user.get_api_data()
+        updated_params.update({'password_confirmation': ''})
+        updated_params.update({'new_password': ''})
+
+        params['last_login'] = updated_params['last_login']
+        if name == 'email':
+            params['emails'] = [attrs['email']]
+        if name == 'extern_type':
+            #cannot update this via form, expected value is original one
+            params['extern_type'] = "rhodecode"
+        if name == 'extern_name':
+            #cannot update this via form, expected value is original one
+            params['extern_name'] = str(user_id)
+        if name == 'active':
+            #my account cannot deactivate account
+            params['active'] = True
+        if name == 'admin':
+            #my account cannot make you an admin !
+            params['admin'] = False
+
+        self.assertEqual(params, updated_params)
+
+    def test_my_account_update_err_email_exists(self):
+        self.log_user()
+
+        new_email = 'test_regular@mail.com'  # already exisitn email
+        response = self.app.post(url('my_account'),
+                                params=dict(
+                                    username='test_admin',
+                                    new_password='test12',
+                                    password_confirmation='test122',
+                                    firstname='NewName',
+                                    lastname='NewLastname',
+                                    email=new_email,)
+                                )
+
+        response.mustcontain('This e-mail address is already taken')
+
+    def test_my_account_update_err(self):
+        self.log_user('test_regular2', 'test12')
+
+        new_email = 'newmail.pl'
+        response = self.app.post(url('my_account'),
+                                 params=dict(
+                                            username='test_admin',
+                                            new_password='test12',
+                                            password_confirmation='test122',
+                                            firstname='NewName',
+                                            lastname='NewLastname',
+                                            email=new_email,))
+
+        response.mustcontain('An email address must contain a single @')
+        from rhodecode.model import validators
+        msg = validators.ValidUsername(edit=False, old_data={})\
+                ._messages['username_exists']
+        msg = h.html_escape(msg % {'username': 'test_admin'})
+        response.mustcontain(u"%s" % msg)
+
+    def test_my_account_api_keys(self):
+        usr = self.log_user('test_regular2', 'test12')
+        user = User.get(usr['user_id'])
+        response = self.app.get(url('my_account_api_keys'))
+        response.mustcontain(user.api_key)
+        response.mustcontain('expires: never')
+
+    @parameterized.expand([
+        ('forever', -1),
+        ('5mins', 60*5),
+        ('30days', 60*60*24*30),
+    ])
+    def test_my_account_add_api_keys(self, desc, lifetime):
+        usr = self.log_user('test_regular2', 'test12')
+        user = User.get(usr['user_id'])
+        response = self.app.post(url('my_account_api_keys'),
+                                 {'description': desc, 'lifetime': lifetime})
+        self.checkSessionFlash(response, 'Api key successfully created')
+        try:
+            response = response.follow()
+            user = User.get(usr['user_id'])
+            for api_key in user.api_keys:
+                response.mustcontain(api_key)
+        finally:
+            for api_key in UserApiKeys.query().all():
+                Session().delete(api_key)
+                Session().commit()
+
+    def test_my_account_remove_api_key(self):
+        usr = self.log_user('test_regular2', 'test12')
+        user = User.get(usr['user_id'])
+        response = self.app.post(url('my_account_api_keys'),
+                                 {'description': 'desc', 'lifetime': -1})
+        self.checkSessionFlash(response, 'Api key successfully created')
+        response = response.follow()
+
+        #now delete our key
+        keys = UserApiKeys.query().all()
+        self.assertEqual(1, len(keys))
+
+        response = self.app.post(url('my_account_api_keys'),
+                 {'_method': 'delete', 'del_api_key': keys[0].api_key})
+        self.checkSessionFlash(response, 'Api key successfully deleted')
+        keys = UserApiKeys.query().all()
+        self.assertEqual(0, len(keys))
+
+
+    def test_my_account_reset_main_api_key(self):
+        usr = self.log_user('test_regular2', 'test12')
+        user = User.get(usr['user_id'])
+        api_key = user.api_key
+        response = self.app.get(url('my_account_api_keys'))
+        response.mustcontain(api_key)
+        response.mustcontain('expires: never')
+
+        response = self.app.post(url('my_account_api_keys'),
+                 {'_method': 'delete', 'del_api_key_builtin': api_key})
+        self.checkSessionFlash(response, 'Api key successfully reset')
+        response = response.follow()
+        response.mustcontain(no=[api_key])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/functional/test_repo_groups.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,33 @@
+from rhodecode.tests import *
+
+
+class TestRepoGroupsController(TestController):
+
+    def test_index(self):
+        self.log_user()
+        response = self.app.get(url('repos_groups'))
+        response.mustcontain('{"totalRecords": 0, "sort": null, "startIndex": 0, "dir": "asc", "records": []};')
+
+#    def test_create(self):
+#        response = self.app.post(url('repos_groups'))
+
+    def test_new(self):
+        self.log_user()
+        response = self.app.get(url('new_repos_group'))
+
+    def test_new_by_regular_user(self):
+        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+        response = self.app.get(url('new_repos_group'), status=403)
+#
+#    def test_update(self):
+#        response = self.app.put(url('repos_group', group_name=1))
+#
+#    def test_delete(self):
+#        self.log_user()
+#        response = self.app.delete(url('repos_group', group_name=1))
+#
+#    def test_show(self):
+#        response = self.app.get(url('repos_group', group_name=1))
+#
+#    def test_edit(self):
+#        response = self.app.get(url('edit_repo_group', group_name=1))
--- a/rhodecode/tests/functional/test_repos_groups.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-from rhodecode.tests import *
-
-
-class TestReposGroupsController(TestController):
-
-    def test_index(self):
-        self.log_user()
-        response = self.app.get(url('repos_groups'))
-        response.mustcontain('There are no repository groups yet')
-
-#    def test_index_as_xml(self):
-#        response = self.app.get(url('formatted_repos_groups', format='xml'))
-#
-#    def test_create(self):
-#        response = self.app.post(url('repos_groups'))
-
-    def test_new(self):
-        self.log_user()
-        response = self.app.get(url('new_repos_group'))
-
-    def test_new_by_regular_user(self):
-        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-        response = self.app.get(url('new_repos_group'), status=403)
-#
-#    def test_new_as_xml(self):
-#        response = self.app.get(url('formatted_new_repos_group', format='xml'))
-#
-#    def test_update(self):
-#        response = self.app.put(url('repos_group', group_name=1))
-#
-#    def test_update_browser_fakeout(self):
-#        response = self.app.post(url('repos_group', group_name=1), params=dict(_method='put'))
-#
-#    def test_delete(self):
-#        self.log_user()
-#        response = self.app.delete(url('repos_group', group_name=1))
-#
-#    def test_delete_browser_fakeout(self):
-#        response = self.app.post(url('repos_group', group_name=1), params=dict(_method='delete'))
-#
-#    def test_show(self):
-#        response = self.app.get(url('repos_group', group_name=1))
-#
-#    def test_show_as_xml(self):
-#        response = self.app.get(url('formatted_repos_group', group_name=1, format='xml'))
-#
-#    def test_edit(self):
-#        response = self.app.get(url('edit_repos_group', group_name=1))
-#
-#    def test_edit_as_xml(self):
-#        response = self.app.get(url('formatted_edit_repos_group', group_name=1, format='xml'))
--- a/rhodecode/tests/functional/test_summary.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/functional/test_summary.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,17 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 from rhodecode.tests import *
 from rhodecode.tests.fixture import Fixture
 from rhodecode.model.db import Repository
@@ -19,33 +33,11 @@
 
         #repo type
         response.mustcontain(
-            """<img style="margin-bottom:2px" class="icon" """
-            """title="Mercurial repository" alt="Mercurial repository" """
-            """src="/images/icons/hgicon.png"/>"""
-        )
-        response.mustcontain(
-            """<img style="margin-bottom:2px" class="icon" """
-            """title="Public repository" alt="Public """
-            """repository" src="/images/icons/public_repo.png"/>"""
+            """<i class="icon-hg" """
         )
-
-        #codes stats
-        self._enable_stats()
-
-        ScmModel().mark_for_invalidation(HG_REPO)
-        response = self.app.get(url(controller='summary', action='index',
-                                    repo_name=HG_REPO))
+        #public/private
         response.mustcontain(
-            """var data = [["py", {"count": 68, "desc": ["Python"]}], """
-            """["rst", {"count": 16, "desc": ["Rst"]}], """
-            """["css", {"count": 2, "desc": ["Css"]}], """
-            """["sh", {"count": 2, "desc": ["Bash"]}], """
-            """["yml", {"count": 1, "desc": ["Yaml"]}], """
-            """["makefile", {"count": 1, "desc": ["Makefile", "Makefile"]}], """
-            """["js", {"count": 1, "desc": ["Javascript"]}], """
-            """["cfg", {"count": 1, "desc": ["Ini"]}], """
-            """["ini", {"count": 1, "desc": ["Ini"]}], """
-            """["html", {"count": 1, "desc": ["EvoqueHtml", "Html"]}]];"""
+            """<i class="icon-unlock-alt">"""
         )
 
         # clone url...
@@ -61,14 +53,11 @@
 
         #repo type
         response.mustcontain(
-            """<img style="margin-bottom:2px" class="icon" """
-            """title="Git repository" alt="Git repository" """
-            """src="/images/icons/giticon.png"/>"""
+            """<i class="icon-git" """
         )
+        #public/private
         response.mustcontain(
-            """<img style="margin-bottom:2px" class="icon" """
-            """title="Public repository" alt="Public """
-            """repository" src="/images/icons/public_repo.png"/>"""
+            """<i class="icon-unlock-alt">"""
         )
 
         # clone url...
@@ -83,12 +72,13 @@
                                     repo_name='_%s' % ID))
 
         #repo type
-        response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
-                        """title="Mercurial repository" alt="Mercurial """
-                        """repository" src="/images/icons/hgicon.png"/>""")
-        response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
-                        """title="Public repository" alt="Public """
-                        """repository" src="/images/icons/public_repo.png"/>""")
+        response.mustcontain(
+            """<i class="icon-hg" """
+        )
+        #public/private
+        response.mustcontain(
+            """<i class="icon-unlock-alt">"""
+        )
 
     def test_index_by_repo_having_id_path_in_name_hg(self):
         self.log_user()
@@ -111,15 +101,76 @@
                                     repo_name='_%s' % ID))
 
         #repo type
-        response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
-                        """title="Git repository" alt="Git """
-                        """repository" src="/images/icons/giticon.png"/>""")
-        response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
-                        """title="Public repository" alt="Public """
-                        """repository" src="/images/icons/public_repo.png"/>""")
+        response.mustcontain(
+            """<i class="icon-git" """
+        )
+        #public/private
+        response.mustcontain(
+            """<i class="icon-unlock-alt">"""
+        )
 
-    def _enable_stats(self):
-        r = Repository.get_by_repo_name(HG_REPO)
+    def _enable_stats(self, repo):
+        r = Repository.get_by_repo_name(repo)
         r.enable_statistics = True
         Session().add(r)
         Session().commit()
+
+    def test_index_trending(self):
+        self.log_user()
+        #codes stats
+        self._enable_stats(HG_REPO)
+
+        ScmModel().mark_for_invalidation(HG_REPO)
+        response = self.app.get(url(controller='summary', action='index',
+                                    repo_name=HG_REPO))
+        response.mustcontain(
+            '[["py", {"count": 68, "desc": ["Python"]}], '
+            '["rst", {"count": 16, "desc": ["Rst"]}], '
+            '["css", {"count": 2, "desc": ["Css"]}], '
+            '["sh", {"count": 2, "desc": ["Bash"]}], '
+            '["yml", {"count": 1, "desc": ["Yaml"]}], '
+            '["makefile", {"count": 1, "desc": ["Makefile", "Makefile"]}], '
+            '["js", {"count": 1, "desc": ["Javascript"]}], '
+            '["cfg", {"count": 1, "desc": ["Ini"]}], '
+            '["ini", {"count": 1, "desc": ["Ini"]}], '
+            '["html", {"count": 1, "desc": ["EvoqueHtml", "Html"]}]];'
+        )
+
+    def test_index_statistics(self):
+        self.log_user()
+        #codes stats
+        self._enable_stats(HG_REPO)
+
+        ScmModel().mark_for_invalidation(HG_REPO)
+        response = self.app.get(url(controller='summary', action='statistics',
+                                    repo_name=HG_REPO))
+
+    def test_index_trending_git(self):
+        self.log_user()
+        #codes stats
+        self._enable_stats(GIT_REPO)
+
+        ScmModel().mark_for_invalidation(GIT_REPO)
+        response = self.app.get(url(controller='summary', action='index',
+                                    repo_name=GIT_REPO))
+        response.mustcontain(
+            '[["py", {"count": 68, "desc": ["Python"]}], '
+            '["rst", {"count": 16, "desc": ["Rst"]}], '
+            '["css", {"count": 2, "desc": ["Css"]}], '
+            '["sh", {"count": 2, "desc": ["Bash"]}], '
+            '["makefile", {"count": 1, "desc": ["Makefile", "Makefile"]}], '
+            '["js", {"count": 1, "desc": ["Javascript"]}], '
+            '["cfg", {"count": 1, "desc": ["Ini"]}], '
+            '["ini", {"count": 1, "desc": ["Ini"]}], '
+            '["html", {"count": 1, "desc": ["EvoqueHtml", "Html"]}], '
+            '["bat", {"count": 1, "desc": ["Batch"]}]];'
+        )
+
+    def test_index_statistics_git(self):
+        self.log_user()
+        #codes stats
+        self._enable_stats(GIT_REPO)
+
+        ScmModel().mark_for_invalidation(GIT_REPO)
+        response = self.app.get(url(controller='summary', action='statistics',
+                                    repo_name=GIT_REPO))
--- a/rhodecode/tests/models/common.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/models/common.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,7 +1,7 @@
 from rhodecode.tests import *
 from rhodecode.tests.fixture import Fixture
 
-from rhodecode.model.repos_group import ReposGroupModel
+from rhodecode.model.repo_group import RepoGroupModel
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.db import RepoGroup, Repository, User
 from rhodecode.model.user import UserModel
@@ -15,12 +15,12 @@
 
 def _destroy_project_tree(test_u1_id):
     Session.remove()
-    repos_group = RepoGroup.get_by_group_name(group_name='g0')
-    for el in reversed(repos_group.recursive_groups_and_repos()):
+    repo_group = RepoGroup.get_by_group_name(group_name='g0')
+    for el in reversed(repo_group.recursive_groups_and_repos()):
         if isinstance(el, Repository):
             RepoModel().delete(el)
         elif isinstance(el, RepoGroup):
-            ReposGroupModel().delete(el, force_delete=True)
+            RepoGroupModel().delete(el, force_delete=True)
 
     u = User.get(test_u1_id)
     Session().delete(u)
@@ -56,25 +56,25 @@
         username=u'test_u1', password=u'qweqwe',
         email=u'test_u1@rhodecode.org', firstname=u'test_u1', lastname=u'test_u1'
     )
-    g0 = fixture.create_group('g0')
-    g0_1 = fixture.create_group('g0_1', group_parent_id=g0)
-    g0_1_1 = fixture.create_group('g0_1_1', group_parent_id=g0_1)
-    g0_1_1_r1 = fixture.create_repo('g0/g0_1/g0_1_1/g0_1_1_r1', repos_group=g0_1_1)
-    g0_1_1_r2 = fixture.create_repo('g0/g0_1/g0_1_1/g0_1_1_r2', repos_group=g0_1_1)
-    g0_1_r1 = fixture.create_repo('g0/g0_1/g0_1_r1', repos_group=g0_1)
-    g0_2 = fixture.create_group('g0_2', group_parent_id=g0)
-    g0_2_r1 = fixture.create_repo('g0/g0_2/g0_2_r1', repos_group=g0_2)
-    g0_2_r2 = fixture.create_repo('g0/g0_2/g0_2_r2', repos_group=g0_2)
-    g0_3 = fixture.create_group('g0_3', group_parent_id=g0)
-    g0_3_r1 = fixture.create_repo('g0/g0_3/g0_3_r1', repos_group=g0_3)
+    g0 = fixture.create_repo_group('g0')
+    g0_1 = fixture.create_repo_group('g0_1', group_parent_id=g0)
+    g0_1_1 = fixture.create_repo_group('g0_1_1', group_parent_id=g0_1)
+    g0_1_1_r1 = fixture.create_repo('g0/g0_1/g0_1_1/g0_1_1_r1', repo_group=g0_1_1)
+    g0_1_1_r2 = fixture.create_repo('g0/g0_1/g0_1_1/g0_1_1_r2', repo_group=g0_1_1)
+    g0_1_r1 = fixture.create_repo('g0/g0_1/g0_1_r1', repo_group=g0_1)
+    g0_2 = fixture.create_repo_group('g0_2', group_parent_id=g0)
+    g0_2_r1 = fixture.create_repo('g0/g0_2/g0_2_r1', repo_group=g0_2)
+    g0_2_r2 = fixture.create_repo('g0/g0_2/g0_2_r2', repo_group=g0_2)
+    g0_3 = fixture.create_repo_group('g0_3', group_parent_id=g0)
+    g0_3_r1 = fixture.create_repo('g0/g0_3/g0_3_r1', repo_group=g0_3)
     g0_3_r2_private = fixture.create_repo('g0/g0_3/g0_3_r1_private',
-                                          repos_group=g0_3, repo_private=True)
+                                          repo_group=g0_3, repo_private=True)
     return test_u1
 
 
 def expected_count(group_name, objects=False):
-    repos_group = RepoGroup.get_by_group_name(group_name=group_name)
-    objs = repos_group.recursive_groups_and_repos()
+    repo_group = RepoGroup.get_by_group_name(group_name=group_name)
+    objs = repo_group.recursive_groups_and_repos()
     if objects:
         return objs
     return len(objs)
@@ -83,7 +83,7 @@
 def _check_expected_count(items, repo_items, expected):
     should_be = len(items + repo_items)
     there_are = len(expected)
-    assert  should_be == there_are, ('%s != %s' % ((items + repo_items), expected))
+    assert should_be == there_are, ('%s != %s' % ((items + repo_items), expected))
 
 
 def check_tree_perms(obj_name, repo_perm, prefix, expected_perm):
@@ -91,11 +91,11 @@
                                     % (obj_name, repo_perm, expected_perm))
 
 
-def _get_perms(filter_='', recursive=True, key=None, test_u1_id=None):
+def _get_perms(filter_='', recursive=None, key=None, test_u1_id=None):
     test_u1 = AuthUser(user_id=test_u1_id)
     for k, v in test_u1.permissions[key].items():
-        if recursive and k.startswith(filter_):
+        if recursive in ['all', 'repos', 'groups'] and k.startswith(filter_):
             yield k, v
-        elif not recursive:
+        elif recursive in ['none']:
             if k == filter_:
                 yield k, v
--- a/rhodecode/tests/models/test_diff_parsers.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/models/test_diff_parsers.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,11 +1,11 @@
 from __future__ import with_statement
-import os
 from rhodecode.tests import *
 from rhodecode.lib.diffs import DiffProcessor, NEW_FILENODE, DEL_FILENODE, \
     MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
+from rhodecode.tests.fixture import Fixture
 
-dn = os.path.dirname
-FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'fixtures')
+fixture = Fixture()
+
 
 DIFF_FIXTURES = {
     'hg_diff_add_single_binary_file.diff': [
@@ -272,8 +272,7 @@
     @parameterized.expand([(x,) for x in DIFF_FIXTURES])
     def test_diff(self, diff_fixture):
 
-        with open(os.path.join(FIXTURES, diff_fixture)) as f:
-            diff = f.read()
+        diff = fixture.load_resource(diff_fixture)
 
         diff_proc = DiffProcessor(diff)
         diff_proc_d = diff_proc.prepare()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/models/test_license.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,68 @@
+from __future__ import with_statement
+
+from rhodecode.tests import *
+from rhodecode.tests.fixture import Fixture
+from rhodecode.lib.compat import json
+from rhodecode.model.license import LicenseModel
+
+fixture = Fixture()
+
+TEST_KEY = ''
+
+
+class LicenseTest(BaseTestCase):
+
+    def setUp(self):
+        global TEST_KEY
+        token = LicenseModel.generate_license_token()
+        TEST_KEY = token
+
+    def test_encryption_decryption(self):
+        test_license = {
+            'foo': 'baar',
+            'signature': 'test'
+        }
+        enc = LicenseModel(key=TEST_KEY).encrypt(json.dumps(test_license))
+        dec = json.loads(LicenseModel(key=TEST_KEY).decrypt(enc))
+        self.assertEqual(test_license, dec)
+
+    def test_signature(self):
+        enc_with_key = '1234567890123456'
+        test_license = {
+            'foo': 'baar',
+            'signature': None
+        }
+        test_license['signature'] = LicenseModel(key=TEST_KEY)\
+            .generate_signature(test_license, enc_with_key)
+
+        enc = LicenseModel(key=TEST_KEY).encrypt(json.dumps(test_license))
+        signature = LicenseModel(key=TEST_KEY).verify(enc, enc_with_key)
+
+        del test_license['signature']
+        self.assertEqual(test_license, signature)
+
+    def test_signature_mismatch(self):
+        enc_with_key = '1234567890123456'
+        test_license = {
+            'foo': 'baar',
+            'signature': 'cnashs62tdsbcsaaisuda6215sagc'
+        }
+
+        enc = LicenseModel(key=TEST_KEY).encrypt(json.dumps(test_license))
+
+        self.assertRaises(TypeError,
+            lambda: LicenseModel(key=TEST_KEY).verify(enc, enc_with_key))
+
+    def test_generate_license_token(self):
+        token = LicenseModel.generate_license_token()
+        self.assertEqual(4, len(token.split('-')))
+
+    def test_get_license_info(self):
+        info = LicenseModel.get_license_info('', '')
+        self.assertEqual(info, {})
+
+    def test_get_license_info_default(self):
+        info = LicenseModel.get_license_info('', '', fill_defaults=True)
+        self.assertEqual(info['users'], 20)
+        self.assertEqual(info['valid_till'], 1421884937.512214)
+        self.assertEqual(info['email'], 'support@rhodecode.com')
--- a/rhodecode/tests/models/test_permissions.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/models/test_permissions.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,13 +1,13 @@
 from rhodecode.tests import *
 from rhodecode.tests.fixture import Fixture
-from rhodecode.model.repos_group import ReposGroupModel
+from rhodecode.model.repo_group import RepoGroupModel
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.db import RepoGroup, User, UserGroupRepoGroupToPerm,\
     Permission, UserToPerm
 from rhodecode.model.user import UserModel
 
 from rhodecode.model.meta import Session
-from rhodecode.model.users_group import UserGroupModel
+from rhodecode.model.user_group import UserGroupModel
 from rhodecode.lib.auth import AuthUser
 from rhodecode.model.permission import PermissionModel
 
@@ -19,6 +19,13 @@
     def __init__(self, methodName='runTest'):
         super(TestPermissions, self).__init__(methodName=methodName)
 
+    @classmethod
+    def setUpClass(cls):
+        #recreate default user to get a clean start
+        PermissionModel().create_default_permissions(user=User.DEFAULT_USER,
+                                                     force=True)
+        Session().commit()
+
     def setUp(self):
         self.u1 = UserModel().create_or_update(
             username=u'u1', password=u'qweqwe',
@@ -48,9 +55,9 @@
         UserModel().delete(self.u3)
         UserModel().delete(self.a1)
         if hasattr(self, 'g1'):
-            ReposGroupModel().delete(self.g1.group_id)
+            RepoGroupModel().delete(self.g1.group_id)
         if hasattr(self, 'g2'):
-            ReposGroupModel().delete(self.g2.group_id)
+            RepoGroupModel().delete(self.g2.group_id)
 
         if hasattr(self, 'ug1'):
             UserGroupModel().delete(self.ug1, force=True)
@@ -80,7 +87,7 @@
         a1_auth = AuthUser(user_id=self.a1.user_id)
         perms = {
             'repositories_groups': {},
-            'global': set([u'hg.admin']),
+            'global': set([u'hg.admin', 'hg.create.write_on_repogroup.true']),
             'repositories': {u'vcs_test_hg': u'repository.admin'}
         }
         self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
@@ -96,8 +103,8 @@
                          perms['repositories'][HG_REPO])
 
     def test_default_group_perms(self):
-        self.g1 = fixture.create_group('test1', skip_if_exists=True)
-        self.g2 = fixture.create_group('test2', skip_if_exists=True)
+        self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
+        self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
         u1_auth = AuthUser(user_id=self.u1.user_id)
         perms = {
             'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
@@ -112,12 +119,12 @@
                          perms['global'])
 
     def test_default_admin_group_perms(self):
-        self.g1 = fixture.create_group('test1', skip_if_exists=True)
-        self.g2 = fixture.create_group('test2', skip_if_exists=True)
+        self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
+        self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
         a1_auth = AuthUser(user_id=self.a1.user_id)
         perms = {
             'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
-            'global': set(['hg.admin']),
+            'global': set(['hg.admin', 'hg.create.write_on_repogroup.true']),
             'repositories': {u'vcs_test_hg': 'repository.admin'}
         }
 
@@ -142,7 +149,7 @@
         # grant perm for group this should not override permission from user
         # since it has explicitly set
         new_perm_gr = 'repository.write'
-        RepoModel().grant_users_group_permission(repo=HG_REPO,
+        RepoModel().grant_user_group_permission(repo=HG_REPO,
                                                  group_name=self.ug1,
                                                  perm=new_perm_gr)
         # check perms
@@ -165,7 +172,7 @@
 
         # grant perm for group this should override default permission from user
         new_perm_gr = 'repository.write'
-        RepoModel().grant_users_group_permission(repo=HG_REPO,
+        RepoModel().grant_user_group_permission(repo=HG_REPO,
                                                  group_name=self.ug1,
                                                  perm=new_perm_gr)
         # check perms
@@ -199,7 +206,7 @@
         # grant perm for group this should NOT override permission from user
         # since it's lower than granted
         new_perm_l = 'repository.read'
-        RepoModel().grant_users_group_permission(repo=HG_REPO,
+        RepoModel().grant_user_group_permission(repo=HG_REPO,
                                                  group_name=self.ug1,
                                                  perm=new_perm_l)
         # check perms
@@ -216,8 +223,8 @@
                          perms['repositories_groups'])
 
     def test_repo_in_group_permissions(self):
-        self.g1 = fixture.create_group('group1', skip_if_exists=True)
-        self.g2 = fixture.create_group('group2', skip_if_exists=True)
+        self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
+        self.g2 = fixture.create_repo_group('group2', skip_if_exists=True)
         # both perms should be read !
         u1_auth = AuthUser(user_id=self.u1.user_id)
         self.assertEqual(u1_auth.permissions['repositories_groups'],
@@ -228,12 +235,12 @@
                  {u'group1': u'group.read', u'group2': u'group.read'})
 
         #Change perms to none for both groups
-        ReposGroupModel().grant_user_permission(repos_group=self.g1,
-                                                user=self.anon,
-                                                perm='group.none')
-        ReposGroupModel().grant_user_permission(repos_group=self.g2,
-                                                user=self.anon,
-                                                perm='group.none')
+        RepoGroupModel().grant_user_permission(repo_group=self.g1,
+                                               user=self.anon,
+                                               perm='group.none')
+        RepoGroupModel().grant_user_permission(repo_group=self.g2,
+                                               user=self.anon,
+                                               perm='group.none')
 
         u1_auth = AuthUser(user_id=self.u1.user_id)
         self.assertEqual(u1_auth.permissions['repositories_groups'],
@@ -247,7 +254,7 @@
         name = RepoGroup.url_sep().join([self.g1.group_name, 'test_perm'])
         self.test_repo = fixture.create_repo(name=name,
                                              repo_type='hg',
-                                             repos_group=self.g1,
+                                             repo_group=self.g1,
                                              cur_user=self.u1,)
 
         u1_auth = AuthUser(user_id=self.u1.user_id)
@@ -259,12 +266,10 @@
                  {u'group1': u'group.none', u'group2': u'group.none'})
 
         #grant permission for u2 !
-        ReposGroupModel().grant_user_permission(repos_group=self.g1,
-                                                user=self.u2,
-                                                perm='group.read')
-        ReposGroupModel().grant_user_permission(repos_group=self.g2,
-                                                user=self.u2,
-                                                perm='group.read')
+        RepoGroupModel().grant_user_permission(repo_group=self.g1, user=self.u2,
+                                               perm='group.read')
+        RepoGroupModel().grant_user_permission(repo_group=self.g2, user=self.u2,
+                                               perm='group.read')
         Session().commit()
         self.assertNotEqual(self.u1, self.u2)
         #u1 and anon should have not change perms while u2 should !
@@ -282,16 +287,16 @@
 
     def test_repo_group_user_as_user_group_member(self):
         # create Group1
-        self.g1 = fixture.create_group('group1', skip_if_exists=True)
+        self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
         a1_auth = AuthUser(user_id=self.anon.user_id)
 
         self.assertEqual(a1_auth.permissions['repositories_groups'],
                          {u'group1': u'group.read'})
 
         # set default permission to none
-        ReposGroupModel().grant_user_permission(repos_group=self.g1,
-                                                user=self.anon,
-                                                perm='group.none')
+        RepoGroupModel().grant_user_permission(repo_group=self.g1,
+                                               user=self.anon,
+                                               perm='group.none')
         # make group
         self.ug1 = fixture.create_user_group('G1')
         # add user to group
@@ -313,9 +318,9 @@
                          {u'group1': u'group.none'})
 
         # grant ug1 read permissions for
-        ReposGroupModel().grant_users_group_permission(repos_group=self.g1,
-                                                       group_name=self.ug1,
-                                                       perm='group.read')
+        RepoGroupModel().grant_user_group_permission(repo_group=self.g1,
+                                                      group_name=self.ug1,
+                                                      perm='group.read')
         Session().commit()
         # check if the
         obj = Session().query(UserGroupRepoGroupToPerm)\
@@ -351,7 +356,7 @@
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
-                              'usergroup.read']))
+                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
 
     def test_inherited_permissions_from_default_on_user_disabled(self):
         user_model = UserModel()
@@ -371,7 +376,7 @@
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
-                              'usergroup.read']))
+                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
 
     def test_non_inherited_permissions_from_default_on_user_enabled(self):
         user_model = UserModel()
@@ -399,7 +404,7 @@
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
-                              'usergroup.read']))
+                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
 
     def test_non_inherited_permissions_from_default_on_user_disabled(self):
         user_model = UserModel()
@@ -427,7 +432,7 @@
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
-                              'usergroup.read']))
+                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
 
     def test_owner_permissions_doesnot_get_overwritten_by_group(self):
         #create repo as USER,
@@ -442,7 +447,7 @@
         #set his permission as user group, he should still be admin
         self.ug1 = fixture.create_user_group('G1')
         UserGroupModel().add_user_to_group(self.ug1, self.u1)
-        RepoModel().grant_users_group_permission(self.test_repo,
+        RepoModel().grant_user_group_permission(self.test_repo,
                                                  group_name=self.ug1,
                                                  perm='repository.none')
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/models/test_repo_groups.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,199 @@
+import os
+from sqlalchemy.exc import IntegrityError
+
+from rhodecode.tests import *
+from rhodecode.tests.fixture import Fixture
+
+from rhodecode.model.repo_group import RepoGroupModel
+from rhodecode.model.repo import RepoModel
+from rhodecode.model.db import RepoGroup
+from rhodecode.model.meta import Session
+
+
+fixture = Fixture()
+
+
+def _update_group(id_, group_name, desc='desc', parent_id=None):
+    form_data = fixture._get_group_create_params(group_name=group_name,
+                                                 group_desc=desc,
+                                                 group_parent_id=parent_id)
+    gr = RepoGroupModel().update(id_, form_data)
+    return gr
+
+
+def _update_repo(name, **kwargs):
+    form_data = fixture._get_repo_create_params(**kwargs)
+    if not 'repo_name' in kwargs:
+        form_data['repo_name'] = name
+    if not 'perms_new' in kwargs:
+        form_data['perms_new'] = []
+    if not 'perms_updates' in kwargs:
+        form_data['perms_updates'] = []
+    r = RepoModel().update(name, **form_data)
+    return r
+
+
+class TestRepoGroups(BaseTestCase):
+
+    def setUp(self):
+        self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
+        self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
+        self.g3 = fixture.create_repo_group('test3', skip_if_exists=True)
+
+    def tearDown(self):
+        Session.remove()
+
+    def __check_path(self, *path):
+        """
+        Checks the path for existance !
+        """
+        path = [TESTS_TMP_PATH] + list(path)
+        path = os.path.join(*path)
+        return os.path.isdir(path)
+
+    def _check_folders(self):
+        print os.listdir(TESTS_TMP_PATH)
+
+    def __delete_group(self, id_):
+        RepoGroupModel().delete(id_)
+
+    def test_create_group(self):
+        g = fixture.create_repo_group('newGroup')
+        Session().commit()
+        self.assertEqual(g.full_path, 'newGroup')
+
+        self.assertTrue(self.__check_path('newGroup'))
+
+    def test_create_same_name_group(self):
+        self.assertRaises(IntegrityError, lambda: fixture.create_repo_group('newGroup'))
+        Session().rollback()
+
+    def test_same_subgroup(self):
+        sg1 = fixture.create_repo_group('sub1', group_parent_id=self.g1.group_id)
+        self.assertEqual(sg1.parent_group, self.g1)
+        self.assertEqual(sg1.full_path, 'test1/sub1')
+        self.assertTrue(self.__check_path('test1', 'sub1'))
+
+        ssg1 = fixture.create_repo_group('subsub1', group_parent_id=sg1.group_id)
+        self.assertEqual(ssg1.parent_group, sg1)
+        self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
+        self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
+
+    def test_remove_group(self):
+        sg1 = fixture.create_repo_group('deleteme')
+        self.__delete_group(sg1.group_id)
+
+        self.assertEqual(RepoGroup.get(sg1.group_id), None)
+        self.assertFalse(self.__check_path('deteteme'))
+
+        sg1 = fixture.create_repo_group('deleteme', group_parent_id=self.g1.group_id)
+        self.__delete_group(sg1.group_id)
+
+        self.assertEqual(RepoGroup.get(sg1.group_id), None)
+        self.assertFalse(self.__check_path('test1', 'deteteme'))
+
+    def test_rename_single_group(self):
+        sg1 = fixture.create_repo_group('initial')
+
+        new_sg1 = _update_group(sg1.group_id, 'after')
+        self.assertTrue(self.__check_path('after'))
+        self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
+
+    def test_update_group_parent(self):
+
+        sg1 = fixture.create_repo_group('initial', group_parent_id=self.g1.group_id)
+
+        new_sg1 = _update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
+        self.assertTrue(self.__check_path('test1', 'after'))
+        self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
+
+        new_sg1 = _update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
+        self.assertTrue(self.__check_path('test3', 'after'))
+        self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
+
+        new_sg1 = _update_group(sg1.group_id, 'hello')
+        self.assertTrue(self.__check_path('hello'))
+
+        self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
+
+    def test_subgrouping_with_repo(self):
+
+        g1 = fixture.create_repo_group('g1')
+        g2 = fixture.create_repo_group('g2')
+        # create new repo
+        r = fixture.create_repo('john')
+
+        self.assertEqual(r.repo_name, 'john')
+        # put repo into group
+        r = _update_repo('john', repo_group=g1.group_id)
+        Session().commit()
+        self.assertEqual(r.repo_name, 'g1/john')
+
+        _update_group(g1.group_id, 'g1', parent_id=g2.group_id)
+        self.assertTrue(self.__check_path('g2', 'g1'))
+
+        # test repo
+        self.assertEqual(r.repo_name, RepoGroup.url_sep().join(['g2', 'g1',
+                                                                r.just_name]))
+
+    def test_move_to_root(self):
+        g1 = fixture.create_repo_group('t11')
+        g2 = fixture.create_repo_group('t22', group_parent_id=g1.group_id)
+
+        self.assertEqual(g2.full_path, 't11/t22')
+        self.assertTrue(self.__check_path('t11', 't22'))
+
+        g2 = _update_group(g2.group_id, 'g22', parent_id=None)
+        Session().commit()
+
+        self.assertEqual(g2.group_name, 'g22')
+        # we moved out group from t1 to '' so it's full path should be 'g2'
+        self.assertEqual(g2.full_path, 'g22')
+        self.assertFalse(self.__check_path('t11', 't22'))
+        self.assertTrue(self.__check_path('g22'))
+
+    def test_rename_top_level_group_in_nested_setup(self):
+        g1 = fixture.create_repo_group('L1')
+        g2 = fixture.create_repo_group('L2', group_parent_id=g1.group_id)
+        g3 = fixture.create_repo_group('L3', group_parent_id=g2.group_id)
+
+        r = fixture.create_repo('L1/L2/L3/L3_REPO', repo_group=g3.group_id)
+
+        ##rename L1 all groups should be now changed
+        _update_group(g1.group_id, 'L1_NEW')
+        Session().commit()
+        self.assertEqual(g1.full_path, 'L1_NEW')
+        self.assertEqual(g2.full_path, 'L1_NEW/L2')
+        self.assertEqual(g3.full_path, 'L1_NEW/L2/L3')
+        self.assertEqual(r.repo_name,  'L1_NEW/L2/L3/L3_REPO')
+
+    def test_change_parent_of_top_level_group_in_nested_setup(self):
+        g1 = fixture.create_repo_group('R1')
+        g2 = fixture.create_repo_group('R2', group_parent_id=g1.group_id)
+        g3 = fixture.create_repo_group('R3', group_parent_id=g2.group_id)
+        g4 = fixture.create_repo_group('R1_NEW')
+
+        r = fixture.create_repo('R1/R2/R3/R3_REPO', repo_group=g3.group_id)
+        ##rename L1 all groups should be now changed
+        _update_group(g1.group_id, 'R1', parent_id=g4.group_id)
+        Session().commit()
+        self.assertEqual(g1.full_path, 'R1_NEW/R1')
+        self.assertEqual(g2.full_path, 'R1_NEW/R1/R2')
+        self.assertEqual(g3.full_path, 'R1_NEW/R1/R2/R3')
+        self.assertEqual(r.repo_name,  'R1_NEW/R1/R2/R3/R3_REPO')
+
+    def test_change_parent_of_top_level_group_in_nested_setup_with_rename(self):
+        g1 = fixture.create_repo_group('X1')
+        g2 = fixture.create_repo_group('X2', group_parent_id=g1.group_id)
+        g3 = fixture.create_repo_group('X3', group_parent_id=g2.group_id)
+        g4 = fixture.create_repo_group('X1_NEW')
+
+        r = fixture.create_repo('X1/X2/X3/X3_REPO', repo_group=g3.group_id)
+
+        ##rename L1 all groups should be now changed
+        _update_group(g1.group_id, 'X1_PRIM', parent_id=g4.group_id)
+        Session().commit()
+        self.assertEqual(g1.full_path, 'X1_NEW/X1_PRIM')
+        self.assertEqual(g2.full_path, 'X1_NEW/X1_PRIM/X2')
+        self.assertEqual(g3.full_path, 'X1_NEW/X1_PRIM/X2/X3')
+        self.assertEqual(r.repo_name,  'X1_NEW/X1_PRIM/X2/X3/X3_REPO')
--- a/rhodecode/tests/models/test_repos_groups.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,199 +0,0 @@
-import os
-from sqlalchemy.exc import IntegrityError
-
-from rhodecode.tests import *
-from rhodecode.tests.fixture import Fixture
-
-from rhodecode.model.repos_group import ReposGroupModel
-from rhodecode.model.repo import RepoModel
-from rhodecode.model.db import RepoGroup
-from rhodecode.model.meta import Session
-
-
-fixture = Fixture()
-
-
-def _update_group(id_, group_name, desc='desc', parent_id=None):
-    form_data = fixture._get_group_create_params(group_name=group_name,
-                                                 group_desc=desc,
-                                                 group_parent_id=parent_id)
-    gr = ReposGroupModel().update(id_, form_data)
-    return gr
-
-
-def _update_repo(name, **kwargs):
-    form_data = fixture._get_repo_create_params(**kwargs)
-    if not 'repo_name' in kwargs:
-        form_data['repo_name'] = name
-    if not 'perms_new' in kwargs:
-        form_data['perms_new'] = []
-    if not 'perms_updates' in kwargs:
-        form_data['perms_updates'] = []
-    r = RepoModel().update(name, **form_data)
-    return r
-
-
-class TestReposGroups(BaseTestCase):
-
-    def setUp(self):
-        self.g1 = fixture.create_group('test1', skip_if_exists=True)
-        self.g2 = fixture.create_group('test2', skip_if_exists=True)
-        self.g3 = fixture.create_group('test3', skip_if_exists=True)
-
-    def tearDown(self):
-        Session.remove()
-
-    def __check_path(self, *path):
-        """
-        Checks the path for existance !
-        """
-        path = [TESTS_TMP_PATH] + list(path)
-        path = os.path.join(*path)
-        return os.path.isdir(path)
-
-    def _check_folders(self):
-        print os.listdir(TESTS_TMP_PATH)
-
-    def __delete_group(self, id_):
-        ReposGroupModel().delete(id_)
-
-    def test_create_group(self):
-        g = fixture.create_group('newGroup')
-        Session().commit()
-        self.assertEqual(g.full_path, 'newGroup')
-
-        self.assertTrue(self.__check_path('newGroup'))
-
-    def test_create_same_name_group(self):
-        self.assertRaises(IntegrityError, lambda: fixture.create_group('newGroup'))
-        Session().rollback()
-
-    def test_same_subgroup(self):
-        sg1 = fixture.create_group('sub1', group_parent_id=self.g1.group_id)
-        self.assertEqual(sg1.parent_group, self.g1)
-        self.assertEqual(sg1.full_path, 'test1/sub1')
-        self.assertTrue(self.__check_path('test1', 'sub1'))
-
-        ssg1 = fixture.create_group('subsub1', group_parent_id=sg1.group_id)
-        self.assertEqual(ssg1.parent_group, sg1)
-        self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
-        self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
-
-    def test_remove_group(self):
-        sg1 = fixture.create_group('deleteme')
-        self.__delete_group(sg1.group_id)
-
-        self.assertEqual(RepoGroup.get(sg1.group_id), None)
-        self.assertFalse(self.__check_path('deteteme'))
-
-        sg1 = fixture.create_group('deleteme', group_parent_id=self.g1.group_id)
-        self.__delete_group(sg1.group_id)
-
-        self.assertEqual(RepoGroup.get(sg1.group_id), None)
-        self.assertFalse(self.__check_path('test1', 'deteteme'))
-
-    def test_rename_single_group(self):
-        sg1 = fixture.create_group('initial')
-
-        new_sg1 = _update_group(sg1.group_id, 'after')
-        self.assertTrue(self.__check_path('after'))
-        self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
-
-    def test_update_group_parent(self):
-
-        sg1 = fixture.create_group('initial', group_parent_id=self.g1.group_id)
-
-        new_sg1 = _update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
-        self.assertTrue(self.__check_path('test1', 'after'))
-        self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
-
-        new_sg1 = _update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
-        self.assertTrue(self.__check_path('test3', 'after'))
-        self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
-
-        new_sg1 = _update_group(sg1.group_id, 'hello')
-        self.assertTrue(self.__check_path('hello'))
-
-        self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
-
-    def test_subgrouping_with_repo(self):
-
-        g1 = fixture.create_group('g1')
-        g2 = fixture.create_group('g2')
-        # create new repo
-        r = fixture.create_repo('john')
-
-        self.assertEqual(r.repo_name, 'john')
-        # put repo into group
-        r = _update_repo('john', repo_group=g1.group_id)
-        Session().commit()
-        self.assertEqual(r.repo_name, 'g1/john')
-
-        _update_group(g1.group_id, 'g1', parent_id=g2.group_id)
-        self.assertTrue(self.__check_path('g2', 'g1'))
-
-        # test repo
-        self.assertEqual(r.repo_name, RepoGroup.url_sep().join(['g2', 'g1',
-                                                                r.just_name]))
-
-    def test_move_to_root(self):
-        g1 = fixture.create_group('t11')
-        g2 = fixture.create_group('t22', group_parent_id=g1.group_id)
-
-        self.assertEqual(g2.full_path, 't11/t22')
-        self.assertTrue(self.__check_path('t11', 't22'))
-
-        g2 = _update_group(g2.group_id, 'g22', parent_id=None)
-        Session().commit()
-
-        self.assertEqual(g2.group_name, 'g22')
-        # we moved out group from t1 to '' so it's full path should be 'g2'
-        self.assertEqual(g2.full_path, 'g22')
-        self.assertFalse(self.__check_path('t11', 't22'))
-        self.assertTrue(self.__check_path('g22'))
-
-    def test_rename_top_level_group_in_nested_setup(self):
-        g1 = fixture.create_group('L1')
-        g2 = fixture.create_group('L2', group_parent_id=g1.group_id)
-        g3 = fixture.create_group('L3', group_parent_id=g2.group_id)
-
-        r = fixture.create_repo('L1/L2/L3/L3_REPO', repo_group=g3.group_id)
-
-        ##rename L1 all groups should be now changed
-        _update_group(g1.group_id, 'L1_NEW')
-        Session().commit()
-        self.assertEqual(g1.full_path, 'L1_NEW')
-        self.assertEqual(g2.full_path, 'L1_NEW/L2')
-        self.assertEqual(g3.full_path, 'L1_NEW/L2/L3')
-        self.assertEqual(r.repo_name,  'L1_NEW/L2/L3/L3_REPO')
-
-    def test_change_parent_of_top_level_group_in_nested_setup(self):
-        g1 = fixture.create_group('R1')
-        g2 = fixture.create_group('R2', group_parent_id=g1.group_id)
-        g3 = fixture.create_group('R3', group_parent_id=g2.group_id)
-        g4 = fixture.create_group('R1_NEW')
-
-        r = fixture.create_repo('R1/R2/R3/R3_REPO', repo_group=g3.group_id)
-        ##rename L1 all groups should be now changed
-        _update_group(g1.group_id, 'R1', parent_id=g4.group_id)
-        Session().commit()
-        self.assertEqual(g1.full_path, 'R1_NEW/R1')
-        self.assertEqual(g2.full_path, 'R1_NEW/R1/R2')
-        self.assertEqual(g3.full_path, 'R1_NEW/R1/R2/R3')
-        self.assertEqual(r.repo_name,  'R1_NEW/R1/R2/R3/R3_REPO')
-
-    def test_change_parent_of_top_level_group_in_nested_setup_with_rename(self):
-        g1 = fixture.create_group('X1')
-        g2 = fixture.create_group('X2', group_parent_id=g1.group_id)
-        g3 = fixture.create_group('X3', group_parent_id=g2.group_id)
-        g4 = fixture.create_group('X1_NEW')
-
-        r = fixture.create_repo('X1/X2/X3/X3_REPO', repo_group=g3.group_id)
-
-        ##rename L1 all groups should be now changed
-        _update_group(g1.group_id, 'X1_PRIM', parent_id=g4.group_id)
-        Session().commit()
-        self.assertEqual(g1.full_path, 'X1_NEW/X1_PRIM')
-        self.assertEqual(g2.full_path, 'X1_NEW/X1_PRIM/X2')
-        self.assertEqual(g3.full_path, 'X1_NEW/X1_PRIM/X2/X3')
-        self.assertEqual(r.repo_name,  'X1_NEW/X1_PRIM/X2/X3/X3_REPO')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/models/test_user_group_permissions_on_repo_groups.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,211 @@
+import functools
+from rhodecode.tests import *
+
+from rhodecode.model.repo_group import RepoGroupModel
+from rhodecode.model.db import RepoGroup
+
+from rhodecode.model.meta import Session
+from nose.tools import with_setup
+from rhodecode.tests.models.common import _create_project_tree, check_tree_perms, \
+    _get_perms, _check_expected_count, expected_count, _destroy_project_tree
+from rhodecode.model.user_group import UserGroupModel
+from rhodecode.tests.fixture import Fixture
+
+fixture = Fixture()
+
+test_u2_id = None
+test_u2_gr_id = None
+_get_repo_perms = None
+_get_group_perms = None
+
+
+def permissions_setup_func(group_name='g0', perm='group.read', recursive='all'):
+    """
+    Resets all permissions to perm attribute
+    """
+    repo_group = RepoGroup.get_by_group_name(group_name=group_name)
+    if not repo_group:
+        raise Exception('Cannot get group %s' % group_name)
+    perms_updates = [[test_u2_gr_id, perm, 'users_group']]
+    RepoGroupModel()._update_permissions(repo_group,
+                                         perms_updates=perms_updates,
+                                         recursive=recursive, check_perms=False)
+    Session().commit()
+
+
+def setup_module():
+    global test_u2_id, test_u2_gr_id, _get_repo_perms, _get_group_perms
+    test_u2 = _create_project_tree()
+    Session().commit()
+    test_u2_id = test_u2.user_id
+
+    gr1 = fixture.create_user_group('perms_group_1')
+    Session().commit()
+    test_u2_gr_id = gr1.users_group_id
+    UserGroupModel().add_user_to_group(gr1, user=test_u2_id)
+    Session().commit()
+
+    _get_repo_perms = functools.partial(_get_perms, key='repositories',
+                                        test_u1_id=test_u2_id)
+    _get_group_perms = functools.partial(_get_perms, key='repositories_groups',
+                                         test_u1_id=test_u2_id)
+
+
+def teardown_module():
+    _destroy_project_tree(test_u2_id)
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_without_recursive_mode():
+    # set permission to g0 non-recursive mode
+    recursive = 'none'
+    group = 'g0'
+    permissions_setup_func(group, 'group.write', recursive=recursive)
+
+    items = [x for x in _get_repo_perms(group, recursive)]
+    expected = 0
+    assert len(items) == expected, ' %s != %s' % (len(items), expected)
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'repository.read'
+
+    items = [x for x in _get_group_perms(group, recursive)]
+    expected = 1
+    assert len(items) == expected, ' %s != %s' % (len(items), expected)
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.write'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_without_recursive_mode_subgroup():
+    # set permission to g0 non-recursive mode
+    recursive = 'none'
+    group = 'g0/g0_1'
+    permissions_setup_func(group, 'group.write', recursive=recursive)
+
+    items = [x for x in _get_repo_perms(group, recursive)]
+    expected = 0
+    assert len(items) == expected, ' %s != %s' % (len(items), expected)
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'repository.read'
+
+    items = [x for x in _get_group_perms(group, recursive)]
+    expected = 1
+    assert len(items) == expected, ' %s != %s' % (len(items), expected)
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.write'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_mode():
+
+    # set permission to g0 recursive mode, all children including
+    # other repos and groups should have this permission now set !
+    recursive = 'all'
+    group = 'g0'
+    permissions_setup_func(group, 'group.write', recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.write'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.write'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_mode_inner_group():
+    ## set permission to g0_3 group to none
+    recursive = 'all'
+    group = 'g0/g0_3'
+    permissions_setup_func(group, 'group.none', recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.none'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.none'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_mode_deepest():
+    ## set permission to g0_3 group to none
+    recursive = 'all'
+    group = 'g0/g0_1/g0_1_1'
+    permissions_setup_func(group, 'group.write', recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.write'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.write'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
+    ## set permission to g0_3 group to none
+    recursive = 'all'
+    group = 'g0/g0_2'
+    permissions_setup_func(group, 'group.admin', recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.admin'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.admin'
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_mode_on_repos():
+    # set permission to g0/g0_1 with recursive mode on just repositories
+    recursive = 'repos'
+    group = 'g0/g0_1'
+    perm = 'group.write'
+    permissions_setup_func(group, perm, recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.write'
+
+    for name, perm in items:
+        # permission is set with repos only mode, but we also change the permission
+        # on the group we trigger the apply to children from, thus we need
+        # to change its permission check
+        old_perm = 'group.read'
+        if name == group:
+            old_perm = perm
+        yield check_tree_perms, name, perm, group, old_perm
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_mode_on_repo_groups():
+    # set permission to g0/g0_1 with recursive mode on just repository groups
+    recursive = 'groups'
+    group = 'g0/g0_1'
+    perm = 'group.none'
+    permissions_setup_func(group, perm, recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.read'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.none'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/models/test_user_groups.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,64 @@
+import os
+from sqlalchemy.exc import IntegrityError
+from rhodecode.model.db import User
+
+from rhodecode.tests import *
+from rhodecode.tests.fixture import Fixture
+
+from rhodecode.model.user_group import UserGroupModel
+from rhodecode.model.meta import Session
+
+
+fixture = Fixture()
+
+
+class TestUserGroups(BaseTestCase):
+
+    def tearDown(self):
+        # delete all groups
+        for gr in UserGroupModel.get_all():
+            fixture.destroy_user_group(gr)
+        Session().commit()
+
+
+    @parameterized.expand([
+        ([], [], [], [], []),
+        ([], ['regular'], [], [], ['regular']),  # no changes of regular
+        (['some_other'], [], [], ['some_other'], []),   # not added to regular group
+        ([], ['regular'], ['container'], ['container'], ['regular', 'container']),
+        ([], ['regular'], [], ['container', 'container2'], ['regular', 'container', 'container2']),
+        ([], ['regular'], ['other'], [], ['regular']),  # remove not used
+        (['some_other'], ['regular'], ['other', 'container'], ['container', 'container2'], ['regular', 'container', 'container2']),
+    ])
+    def test_enforce_groups(self, pre_existing, regular_should_be,
+                            external_should_be, groups, expected):
+        # delete all groups
+        for gr in UserGroupModel.get_all():
+            fixture.destroy_user_group(gr)
+        Session().commit()
+
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        for gr in pre_existing:
+            gr = fixture.create_user_group(gr)
+        Session().commit()
+
+        # make sure use is just in those groups
+        for gr in regular_should_be:
+            gr = fixture.create_user_group(gr)
+            Session().commit()
+            UserGroupModel().add_user_to_group(gr, user)
+            Session().commit()
+
+        # now special external groups created by auth plugins
+        for gr in external_should_be:
+            gr = fixture.create_user_group(gr, user_group_data={'extern_type': 'container'})
+            Session().commit()
+            UserGroupModel().add_user_to_group(gr, user)
+            Session().commit()
+
+        UserGroupModel().enforce_groups(user, groups, 'container')
+        Session().commit()
+
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        in_groups = user.group_member
+        self.assertEqual(expected, [x.users_group.users_group_name for x in in_groups])
--- a/rhodecode/tests/models/test_user_permissions_on_groups.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,190 +0,0 @@
-import functools
-from rhodecode.tests import *
-
-from rhodecode.model.repos_group import ReposGroupModel
-from rhodecode.model.db import RepoGroup, Repository, User
-
-from rhodecode.model.meta import Session
-from nose.tools import with_setup
-from rhodecode.tests.models.common import _create_project_tree, check_tree_perms, \
-    _get_perms, _check_expected_count, expected_count, _destroy_project_tree
-
-
-test_u1_id = None
-_get_repo_perms = None
-_get_group_perms = None
-
-
-def permissions_setup_func(group_name='g0', perm='group.read', recursive=True,
-                           user_id=None):
-    """
-    Resets all permissions to perm attribute
-    """
-    if not user_id:
-        user_id = test_u1_id
-    repos_group = RepoGroup.get_by_group_name(group_name=group_name)
-    if not repos_group:
-        raise Exception('Cannot get group %s' % group_name)
-    perms_updates = [[user_id, perm, 'user']]
-    ReposGroupModel()._update_permissions(repos_group,
-                                          perms_updates=perms_updates,
-                                          recursive=recursive,
-                                          check_perms=False)
-    Session().commit()
-
-
-def setup_module():
-    global test_u1_id, _get_repo_perms, _get_group_perms
-    test_u1 = _create_project_tree()
-    Session().commit()
-    test_u1_id = test_u1.user_id
-    _get_repo_perms = functools.partial(_get_perms, key='repositories',
-                                        test_u1_id=test_u1_id)
-    _get_group_perms = functools.partial(_get_perms, key='repositories_groups',
-                                         test_u1_id=test_u1_id)
-
-
-def teardown_module():
-    _destroy_project_tree(test_u1_id)
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_without_recursive_mode():
-    # set permission to g0 non-recursive mode
-    recursive = False
-    group = 'g0'
-    permissions_setup_func(group, 'group.write', recursive=recursive)
-
-    items = [x for x in _get_repo_perms(group, recursive)]
-    expected = 0
-    assert len(items) == expected, ' %s != %s' % (len(items), expected)
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'repository.read'
-
-    items = [x for x in _get_group_perms(group, recursive)]
-    expected = 1
-    assert len(items) == expected, ' %s != %s' % (len(items), expected)
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_without_recursive_mode_subgroup():
-    # set permission to g0 non-recursive mode
-    recursive = False
-    group = 'g0/g0_1'
-    permissions_setup_func(group, 'group.write', recursive=recursive)
-
-    items = [x for x in _get_repo_perms(group, recursive)]
-    expected = 0
-    assert len(items) == expected, ' %s != %s' % (len(items), expected)
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'repository.read'
-
-    items = [x for x in _get_group_perms(group, recursive)]
-    expected = 1
-    assert len(items) == expected, ' %s != %s' % (len(items), expected)
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_with_recursive_mode():
-
-    # set permission to g0 recursive mode, all children including
-    # other repos and groups should have this permission now set !
-    recursive = True
-    group = 'g0'
-    permissions_setup_func(group, 'group.write', recursive=recursive)
-
-    repo_items = [x for x in _get_repo_perms(group, recursive)]
-    items = [x for x in _get_group_perms(group, recursive)]
-    _check_expected_count(items, repo_items, expected_count(group, True))
-
-    for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.write'
-
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_with_recursive_mode_for_default_user():
-
-    # set permission to g0 recursive mode, all children including
-    # other repos and groups should have this permission now set !
-    recursive = True
-    group = 'g0'
-    default_user_id = User.get_default_user().user_id
-    permissions_setup_func(group, 'group.write', recursive=recursive,
-                           user_id=default_user_id)
-
-    # change default to get perms for default user
-    _get_repo_perms = functools.partial(_get_perms, key='repositories',
-                                        test_u1_id=default_user_id)
-    _get_group_perms = functools.partial(_get_perms, key='repositories_groups',
-                                         test_u1_id=default_user_id)
-
-    repo_items = [x for x in _get_repo_perms(group, recursive)]
-    items = [x for x in _get_group_perms(group, recursive)]
-    _check_expected_count(items, repo_items, expected_count(group, True))
-
-    for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.write'
-
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_with_recursive_mode_inner_group():
-    ## set permission to g0_3 group to none
-    recursive = True
-    group = 'g0/g0_3'
-    permissions_setup_func(group, 'group.none', recursive=recursive)
-
-    repo_items = [x for x in _get_repo_perms(group, recursive)]
-    items = [x for x in _get_group_perms(group, recursive)]
-    _check_expected_count(items, repo_items, expected_count(group, True))
-
-    for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.none'
-
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.none'
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_with_recursive_mode_deepest():
-    ## set permission to g0_3 group to none
-    recursive = True
-    group = 'g0/g0_1/g0_1_1'
-    permissions_setup_func(group, 'group.write', recursive=recursive)
-
-    repo_items = [x for x in _get_repo_perms(group, recursive)]
-    items = [x for x in _get_group_perms(group, recursive)]
-    _check_expected_count(items, repo_items, expected_count(group, True))
-
-    for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.write'
-
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
-    ## set permission to g0_3 group to none
-    recursive = True
-    group = 'g0/g0_2'
-    permissions_setup_func(group, 'group.admin', recursive=recursive)
-
-    repo_items = [x for x in _get_repo_perms(group, recursive)]
-    items = [x for x in _get_group_perms(group, recursive)]
-    _check_expected_count(items, repo_items, expected_count(group, True))
-
-    for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.admin'
-
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.admin'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/models/test_user_permissions_on_repo_groups.py	Wed Jul 02 19:03:13 2014 -0400
@@ -0,0 +1,299 @@
+import functools
+from rhodecode.tests import *
+
+from rhodecode.model.repo_group import RepoGroupModel
+from rhodecode.model.db import RepoGroup, Repository, User
+
+from rhodecode.model.meta import Session
+from nose.tools import with_setup
+from rhodecode.tests.models.common import _create_project_tree, check_tree_perms, \
+    _get_perms, _check_expected_count, expected_count, _destroy_project_tree
+
+
+test_u1_id = None
+_get_repo_perms = None
+_get_group_perms = None
+
+
+def permissions_setup_func(group_name='g0', perm='group.read', recursive='all',
+                           user_id=None):
+    """
+    Resets all permissions to perm attribute
+    """
+    if not user_id:
+        user_id = test_u1_id
+        # called by the @with_setup decorator also reset the default user stuff
+        permissions_setup_func(group_name, perm, recursive,
+                               user_id=User.get_default_user().user_id)
+
+    repo_group = RepoGroup.get_by_group_name(group_name=group_name)
+    if not repo_group:
+        raise Exception('Cannot get group %s' % group_name)
+
+    perms_updates = [[user_id, perm, 'user']]
+    RepoGroupModel()._update_permissions(repo_group,
+                                         perms_updates=perms_updates,
+                                         recursive=recursive, check_perms=False)
+    Session().commit()
+
+def setup_module():
+    global test_u1_id, _get_repo_perms, _get_group_perms
+    test_u1 = _create_project_tree()
+    Session().commit()
+    test_u1_id = test_u1.user_id
+    _get_repo_perms = functools.partial(_get_perms, key='repositories',
+                                        test_u1_id=test_u1_id)
+    _get_group_perms = functools.partial(_get_perms, key='repositories_groups',
+                                         test_u1_id=test_u1_id)
+
+
+def teardown_module():
+    _destroy_project_tree(test_u1_id)
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_without_recursive_mode():
+    # set permission to g0 non-recursive mode
+    recursive = 'none'
+    group = 'g0'
+    permissions_setup_func(group, 'group.write', recursive=recursive)
+
+    items = [x for x in _get_repo_perms(group, recursive)]
+    expected = 0
+    assert len(items) == expected, ' %s != %s' % (len(items), expected)
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'repository.read'
+
+    items = [x for x in _get_group_perms(group, recursive)]
+    expected = 1
+    assert len(items) == expected, ' %s != %s' % (len(items), expected)
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.write'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_without_recursive_mode_subgroup():
+    # set permission to g0 non-recursive mode
+    recursive = 'none'
+    group = 'g0/g0_1'
+    permissions_setup_func(group, 'group.write', recursive=recursive)
+
+    items = [x for x in _get_repo_perms(group, recursive)]
+    expected = 0
+    assert len(items) == expected, ' %s != %s' % (len(items), expected)
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'repository.read'
+
+    items = [x for x in _get_group_perms(group, recursive)]
+    expected = 1
+    assert len(items) == expected, ' %s != %s' % (len(items), expected)
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.write'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_mode():
+
+    # set permission to g0 recursive mode, all children including
+    # other repos and groups should have this permission now set !
+    recursive = 'all'
+    group = 'g0'
+    permissions_setup_func(group, 'group.write', recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.write'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.write'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_mode_for_default_user():
+
+    # set permission to g0 recursive mode, all children including
+    # other repos and groups should have this permission now set !
+    recursive = 'all'
+    group = 'g0'
+    default_user_id = User.get_default_user().user_id
+    permissions_setup_func(group, 'group.write', recursive=recursive,
+                           user_id=default_user_id)
+
+    # change default to get perms for default user
+    _get_repo_perms = functools.partial(_get_perms, key='repositories',
+                                        test_u1_id=default_user_id)
+    _get_group_perms = functools.partial(_get_perms, key='repositories_groups',
+                                         test_u1_id=default_user_id)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.write'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.write'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_mode_inner_group():
+    ## set permission to g0_3 group to none
+    recursive = 'all'
+    group = 'g0/g0_3'
+    permissions_setup_func(group, 'group.none', recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.none'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.none'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_mode_deepest():
+    ## set permission to g0_3 group to none
+    recursive = 'all'
+    group = 'g0/g0_1/g0_1_1'
+    permissions_setup_func(group, 'group.write', recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.write'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.write'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
+    ## set permission to g0_3 group to none
+    recursive = 'all'
+    group = 'g0/g0_2'
+    permissions_setup_func(group, 'group.admin', recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.admin'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.admin'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_repo_mode_for_default_user():
+    # set permission to g0/g0_1 recursive repos only mode, all children including
+    # other repos should have this permission now set, inner groups are excluded!
+    recursive = 'repos'
+    group = 'g0/g0_1'
+    perm = 'group.none'
+    default_user_id = User.get_default_user().user_id
+
+    permissions_setup_func(group, perm, recursive=recursive,
+                           user_id=default_user_id)
+
+    # change default to get perms for default user
+    _get_repo_perms = functools.partial(_get_perms, key='repositories',
+                                        test_u1_id=default_user_id)
+    _get_group_perms = functools.partial(_get_perms, key='repositories_groups',
+                                         test_u1_id=default_user_id)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.none'
+
+    for name, perm in items:
+        # permission is set with repos only mode, but we also change the permission
+        # on the group we trigger the apply to children from, thus we need
+        # to change its permission check
+        old_perm = 'group.read'
+        if name == group:
+            old_perm = perm
+        yield check_tree_perms, name, perm, group, old_perm
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_repo_mode_inner_group():
+    ## set permission to g0_3 group to none, with recursive repos only
+    recursive = 'repos'
+    group = 'g0/g0_3'
+    perm = 'group.none'
+    permissions_setup_func(group, perm, recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.none'
+
+    for name, perm in items:
+        # permission is set with repos only mode, but we also change the permission
+        # on the group we trigger the apply to children from, thus we need
+        # to change its permission check
+        old_perm = 'group.read'
+        if name == group:
+            old_perm = perm
+        yield check_tree_perms, name, perm, group, old_perm
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_group_mode_for_default_user():
+    # set permission to g0/g0_1 with recursive groups only mode, all children including
+    # other groups should have this permission now set. repositories should
+    # remain intact as we use groups only mode !
+    recursive = 'groups'
+    group = 'g0/g0_1'
+    default_user_id = User.get_default_user().user_id
+    permissions_setup_func(group, 'group.write', recursive=recursive,
+                           user_id=default_user_id)
+
+    # change default to get perms for default user
+    _get_repo_perms = functools.partial(_get_perms, key='repositories',
+                                        test_u1_id=default_user_id)
+    _get_group_perms = functools.partial(_get_perms, key='repositories_groups',
+                                         test_u1_id=default_user_id)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.read'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.write'
+
+
+@with_setup(permissions_setup_func)
+def test_user_permissions_on_group_with_recursive_group_mode_inner_group():
+    ## set permission to g0_3 group to none, with recursive mode for groups only
+    recursive = 'groups'
+    group = 'g0/g0_3'
+    permissions_setup_func(group, 'group.none', recursive=recursive)
+
+    repo_items = [x for x in _get_repo_perms(group, recursive)]
+    items = [x for x in _get_group_perms(group, recursive)]
+    _check_expected_count(items, repo_items, expected_count(group, True))
+
+    for name, perm in repo_items:
+        yield check_tree_perms, name, perm, group, 'repository.read'
+
+    for name, perm in items:
+        yield check_tree_perms, name, perm, group, 'group.none'
--- a/rhodecode/tests/models/test_users.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/models/test_users.py	Wed Jul 02 19:03:13 2014 -0400
@@ -5,7 +5,7 @@
 from rhodecode.model.user import UserModel
 
 from rhodecode.model.meta import Session
-from rhodecode.model.users_group import UserGroupModel
+from rhodecode.model.user_group import UserGroupModel
 from rhodecode.tests.fixture import Fixture
 
 fixture = Fixture()
@@ -22,19 +22,19 @@
     def test_create_and_remove(self):
         usr = UserModel().create_or_update(username=u'test_user',
                                            password=u'qweqwe',
-                                     email=u'u232@rhodecode.org',
-                                     firstname=u'u1', lastname=u'u1')
+                                           email=u'u232@rhodecode.org',
+                                           firstname=u'u1', lastname=u'u1')
         Session().commit()
         self.assertEqual(User.get_by_username(u'test_user'), usr)
 
         # make user group
-        users_group = fixture.create_user_group('some_example_group')
+        user_group = fixture.create_user_group('some_example_group')
         Session().commit()
 
-        UserGroupModel().add_user_to_group(users_group, usr)
+        UserGroupModel().add_user_to_group(user_group, usr)
         Session().commit()
 
-        self.assertEqual(UserGroup.get(users_group.users_group_id), users_group)
+        self.assertEqual(UserGroup.get(user_group.users_group_id), user_group)
         self.assertEqual(UserGroupMember.query().count(), 1)
         UserModel().delete(usr.user_id)
         Session().commit()
--- a/rhodecode/tests/models/test_users_group_permissions_on_groups.py	Wed Jul 02 19:03:10 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,170 +0,0 @@
-import functools
-from rhodecode.tests import *
-
-from rhodecode.model.repos_group import ReposGroupModel
-from rhodecode.model.db import RepoGroup
-
-from rhodecode.model.meta import Session
-from nose.tools import with_setup
-from rhodecode.tests.models.common import _create_project_tree, check_tree_perms, \
-    _get_perms, _check_expected_count, expected_count, _destroy_project_tree
-from rhodecode.model.users_group import UserGroupModel
-from rhodecode.tests.fixture import Fixture
-
-fixture = Fixture()
-
-test_u2_id = None
-test_u2_gr_id = None
-_get_repo_perms = None
-_get_group_perms = None
-
-
-def permissions_setup_func(group_name='g0', perm='group.read', recursive=True):
-    """
-    Resets all permissions to perm attribute
-    """
-    repos_group = RepoGroup.get_by_group_name(group_name=group_name)
-    if not repos_group:
-        raise Exception('Cannot get group %s' % group_name)
-    perms_updates = [[test_u2_gr_id, perm, 'users_group']]
-    ReposGroupModel()._update_permissions(repos_group,
-                                          perms_updates=perms_updates,
-                                          recursive=recursive,
-                                          check_perms=False)
-    Session().commit()
-
-
-def setup_module():
-    global test_u2_id, test_u2_gr_id, _get_repo_perms, _get_group_perms
-    test_u2 = _create_project_tree()
-    Session().commit()
-    test_u2_id = test_u2.user_id
-
-    gr1 = fixture.create_user_group('perms_group_1')
-    Session().commit()
-    test_u2_gr_id = gr1.users_group_id
-    UserGroupModel().add_user_to_group(gr1, user=test_u2_id)
-    Session().commit()
-
-    _get_repo_perms = functools.partial(_get_perms, key='repositories',
-                                        test_u1_id=test_u2_id)
-    _get_group_perms = functools.partial(_get_perms, key='repositories_groups',
-                                         test_u1_id=test_u2_id)
-
-
-def teardown_module():
-    _destroy_project_tree(test_u2_id)
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_without_recursive_mode():
-    # set permission to g0 non-recursive mode
-    recursive = False
-    group = 'g0'
-    permissions_setup_func(group, 'group.write', recursive=recursive)
-
-    items = [x for x in _get_repo_perms(group, recursive)]
-    expected = 0
-    assert len(items) == expected, ' %s != %s' % (len(items), expected)
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'repository.read'
-
-    items = [x for x in _get_group_perms(group, recursive)]
-    expected = 1
-    assert len(items) == expected, ' %s != %s' % (len(items), expected)
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_without_recursive_mode_subgroup():
-    # set permission to g0 non-recursive mode
-    recursive = False
-    group = 'g0/g0_1'
-    permissions_setup_func(group, 'group.write', recursive=recursive)
-
-    items = [x for x in _get_repo_perms(group, recursive)]
-    expected = 0
-    assert len(items) == expected, ' %s != %s' % (len(items), expected)
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'repository.read'
-
-    items = [x for x in _get_group_perms(group, recursive)]
-    expected = 1
-    assert len(items) == expected, ' %s != %s' % (len(items), expected)
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_with_recursive_mode():
-
-    # set permission to g0 recursive mode, all children including
-    # other repos and groups should have this permission now set !
-    recursive = True
-    group = 'g0'
-    permissions_setup_func(group, 'group.write', recursive=recursive)
-
-    repo_items = [x for x in _get_repo_perms(group, recursive)]
-    items = [x for x in _get_group_perms(group, recursive)]
-    _check_expected_count(items, repo_items, expected_count(group, True))
-
-    for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.write'
-
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_with_recursive_mode_inner_group():
-    ## set permission to g0_3 group to none
-    recursive = True
-    group = 'g0/g0_3'
-    permissions_setup_func(group, 'group.none', recursive=recursive)
-
-    repo_items = [x for x in _get_repo_perms(group, recursive)]
-    items = [x for x in _get_group_perms(group, recursive)]
-    _check_expected_count(items, repo_items, expected_count(group, True))
-
-    for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.none'
-
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.none'
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_with_recursive_mode_deepest():
-    ## set permission to g0_3 group to none
-    recursive = True
-    group = 'g0/g0_1/g0_1_1'
-    permissions_setup_func(group, 'group.write', recursive=recursive)
-
-    repo_items = [x for x in _get_repo_perms(group, recursive)]
-    items = [x for x in _get_group_perms(group, recursive)]
-    _check_expected_count(items, repo_items, expected_count(group, True))
-
-    for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.write'
-
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
-
-
-@with_setup(permissions_setup_func)
-def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
-    ## set permission to g0_3 group to none
-    recursive = True
-    group = 'g0/g0_2'
-    permissions_setup_func(group, 'group.admin', recursive=recursive)
-
-    repo_items = [x for x in _get_repo_perms(group, recursive)]
-    items = [x for x in _get_group_perms(group, recursive)]
-    _check_expected_count(items, repo_items, expected_count(group, True))
-
-    for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.admin'
-
-    for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.admin'
--- a/rhodecode/tests/other/test_libs.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/other/test_libs.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.tests.test_libs
-    ~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
-    Package for testing various lib/helper functions in rhodecode
-
-    :created_on: Jun 9, 2011
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,11 +11,25 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.tests.test_libs
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Package for testing various lib/helper functions in rhodecode
+
+:created_on: Jun 9, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
+
 from __future__ import with_statement
 import datetime
 import hashlib
 import mock
 from rhodecode.tests import *
+from rhodecode.lib.utils2 import AttributeDict
+from rhodecode.model.db import Repository
 
 proto = 'http'
 TEST_URLS = [
@@ -170,49 +173,75 @@
         from rhodecode.lib.helpers import gravatar_url
         _md5 = lambda s: hashlib.md5(s).hexdigest()
 
-        def fake_conf(**kwargs):
-            from pylons import config
-            config = {}
-            config['use_gravatar'] = True
-            config.update(kwargs)
-            return config
-
-        class fake_url():
+        #mock pylons.url
+        class fake_url(object):
             @classmethod
             def current(cls, *args, **kwargs):
                 return 'https://server.com'
 
+        #mock pylons.tmpl_context
+        def fake_tmpl_context(_url):
+            _c = AttributeDict()
+            _c.visual = AttributeDict()
+            _c.visual.use_gravatar = True
+            _c.visual.gravatar_url = _url
+
+            return _c
+
+
         with mock.patch('pylons.url', fake_url):
-            fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
-            with mock.patch('rhodecode.CONFIG', fake):
+            fake = fake_tmpl_context(_url='http://test.com/{email}')
+            with mock.patch('pylons.tmpl_context', fake):
                     from pylons import url
                     assert url.current() == 'https://server.com'
                     grav = gravatar_url(email_address='test@foo.com', size=24)
                     assert grav == 'http://test.com/test@foo.com'
 
-            fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
-            with mock.patch('rhodecode.CONFIG', fake):
+            fake = fake_tmpl_context(_url='http://test.com/{email}')
+            with mock.patch('pylons.tmpl_context', fake):
                 grav = gravatar_url(email_address='test@foo.com', size=24)
                 assert grav == 'http://test.com/test@foo.com'
 
-            fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}')
-            with mock.patch('rhodecode.CONFIG', fake):
+            fake = fake_tmpl_context(_url='http://test.com/{md5email}')
+            with mock.patch('pylons.tmpl_context', fake):
                 em = 'test@foo.com'
                 grav = gravatar_url(email_address=em, size=24)
                 assert grav == 'http://test.com/%s' % (_md5(em))
 
-            fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}/{size}')
-            with mock.patch('rhodecode.CONFIG', fake):
+            fake = fake_tmpl_context(_url='http://test.com/{md5email}/{size}')
+            with mock.patch('pylons.tmpl_context', fake):
                 em = 'test@foo.com'
                 grav = gravatar_url(email_address=em, size=24)
                 assert grav == 'http://test.com/%s/%s' % (_md5(em), 24)
 
-            fake = fake_conf(alternative_gravatar_url='{scheme}://{netloc}/{md5email}/{size}')
-            with mock.patch('rhodecode.CONFIG', fake):
+            fake = fake_tmpl_context(_url='{scheme}://{netloc}/{md5email}/{size}')
+            with mock.patch('pylons.tmpl_context', fake):
                 em = 'test@foo.com'
                 grav = gravatar_url(email_address=em, size=24)
                 assert grav == 'https://server.com/%s/%s' % (_md5(em), 24)
 
+    @parameterized.expand([
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '', 'http://vps1:8000/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '/rc', 'http://vps1:8000/rc/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc', 'http://user@vps1:8000/rc/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc', 'http://marcink@vps1:8000/rc/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc/', 'http://user@vps1:8000/rc/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc/', 'http://marcink@vps1:8000/rc/group/repo1'),
+        ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {}, '', 'http://vps1:8000/_23'),
+        ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
+        ('http://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
+        ('http://{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://vps1:8000/_23'),
+        ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://marcink@proxy1.server.com/group/repo1'),
+        ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {}, '', 'https://proxy1.server.com/group/repo1'),
+        ('https://proxy1.server.com/{user}/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://proxy1.server.com/marcink/group/repo1'),
+    ])
+    def test_clone_url_generator(self, tmpl, repo_name, overrides, prefix, expected):
+        from rhodecode.lib.utils2 import get_clone_url
+        clone_url = get_clone_url(uri_tmpl=tmpl, qualifed_home_url='http://vps1:8000'+prefix,
+                                  repo_name=repo_name, repo_id=23, **overrides)
+        self.assertEqual(clone_url, expected)
+
     def _quick_url(self, text, tmpl="""<a class="revision-link" href="%s">%s</a>""", url_=None):
         """
         Changes `some text url[foo]` => `some text <a href="/">foo</a>
@@ -292,3 +321,27 @@
         expected = self._quick_url(expected,
                                    tmpl="""<a href="%s">%s</a>""", url_=url_)
         self.assertEqual(urlify_text(sample), expected)
+
+    @parameterized.expand([
+      ("", None),
+      ("/_2", '2'),
+      ("_2", '2'),
+      ("/_2/", '2'),
+      ("_2/", '2'),
+
+      ("/_21", '21'),
+      ("_21", '21'),
+      ("/_21/", '21'),
+      ("_21/", '21'),
+
+      ("/_21/foobar", '21'),
+      ("_21/121", '21'),
+      ("/_21/_12", '21'),
+      ("_21/rc/foo", '21'),
+
+    ])
+    def test_get_repo_by_id(self, test, expected):
+        from rhodecode.lib.utils import _extract_id_from_repo_name
+        _test = _extract_id_from_repo_name(test)
+        self.assertEqual(_test, expected, msg='url:%s, got:`%s` expected: `%s`'
+                                              % (test, _test, expected))
--- a/rhodecode/tests/other/test_validators.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/other/test_validators.py	Wed Jul 02 19:03:13 2014 -0400
@@ -4,10 +4,10 @@
 from rhodecode.tests import *
 
 from rhodecode.model import validators as v
-from rhodecode.model.users_group import UserGroupModel
+from rhodecode.model.user_group import UserGroupModel
 
 from rhodecode.model.meta import Session
-from rhodecode.model.repos_group import ReposGroupModel
+from rhodecode.model.repo_group import RepoGroupModel
 from rhodecode.model.db import ChangesetStatus, Repository
 from rhodecode.model.changeset_status import ChangesetStatusModel
 from rhodecode.tests.fixture import Fixture
@@ -15,7 +15,7 @@
 fixture = Fixture()
 
 
-class TestReposGroups(BaseTestCase):
+class TestRepoGroups(BaseTestCase):
 
     def setUp(self):
         pass
@@ -72,9 +72,9 @@
         UserGroupModel().delete(gr2)
         Session().commit()
 
-    def test_ValidReposGroup(self):
-        validator = v.ValidReposGroup()
-        model = ReposGroupModel()
+    def test_ValidRepoGroup(self):
+        validator = v.ValidRepoGroup()
+        model = RepoGroupModel()
         self.assertRaises(formencode.Invalid, validator.to_python,
                           {'group_name': HG_REPO, })
         gr = model.create(group_name='test_gr', group_description='desc',
@@ -84,7 +84,7 @@
         self.assertRaises(formencode.Invalid,
                           validator.to_python, {'group_name': gr.group_name, })
 
-        validator = v.ValidReposGroup(edit=True,
+        validator = v.ValidRepoGroup(edit=True,
                                       old_data={'group_id':  gr.group_id})
         self.assertRaises(formencode.Invalid,
                           validator.to_python, {
@@ -149,7 +149,7 @@
         self.assertRaises(formencode.Invalid,
                           validator.to_python, {'repo_name': HG_REPO})
 
-        gr = ReposGroupModel().create(group_name='group_test',
+        gr = RepoGroupModel().create(group_name='group_test',
                                       group_description='desc',
                                       parent=None,
                                       owner=TEST_USER_ADMIN_LOGIN)
--- a/rhodecode/tests/other/test_vcs_operations.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/other/test_vcs_operations.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,19 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.tests.test_scm_operations
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Test suite for making push/pull operations.
-    Run using after doing paster serve test.ini::
-     RC_WHOOSH_TEST_DISABLE=1 RC_NO_TMP_PATH=1 nosetests rhodecode/tests/scripts/test_vcs_operations.py
-
-    You must have git > 1.8.1 for tests to work fine
-
-    :created_on: Dec 30, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -26,20 +11,32 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.tests.test_scm_operations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-import os
+Test suite for making push/pull operations.
+Run using after doing paster serve test.ini::
+ RC_WHOOSH_TEST_DISABLE=1 RC_NO_TMP_PATH=1 nosetests rhodecode/tests/other/test_vcs_operations.py
+
+You must have git > 1.8.1 for tests to work fine
+
+:created_on: Dec 30, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+"""
+
 import tempfile
-import unittest
 import time
 from os.path import join as jn
-from os.path import dirname as dn
 
 from tempfile import _RandomNameSequence
 from subprocess import Popen, PIPE
 
 from rhodecode.tests import *
-from rhodecode.model.db import User, Repository, UserLog, UserIpMap,\
-    CacheInvalidation
+from rhodecode.model.db import User, Repository, UserIpMap, CacheInvalidation
 from rhodecode.model.meta import Session
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.user import UserModel
@@ -115,11 +112,13 @@
                 i, author_str, added_file
             )
         elif vcs == 'git':
-            cmd = """git commit -m 'commited new %s' --author '%s' %s """ % (
+            cmd = """EMAIL="me@email.com" git commit -m 'commited new %s' --author '%s' %s """ % (
                 i, author_str, added_file
             )
         Command(cwd).execute(cmd)
+
     # PUSH it back
+    _REPO = None
     if vcs == 'hg':
         _REPO = HG_REPO
     elif vcs == 'git':
@@ -129,6 +128,7 @@
     clone_url = _construct_url(_REPO, **kwargs)
     if 'clone_url' in kwargs:
         clone_url = kwargs['clone_url']
+    stdout = stderr = None
     if vcs == 'hg':
         stdout, stderr = Command(cwd).execute('hg push --verbose', clone_url)
     elif vcs == 'git':
@@ -151,6 +151,15 @@
 # TESTS
 #==============================================================================
 
+
+def _check_proper_git_push(stdout, stderr):
+    #WTF GIT stderr is output ?!
+    assert 'fatal' not in stderr
+    assert 'rejected' not in stderr
+    assert 'Pushing to' in stderr
+    assert 'master -> master' in stderr
+
+
 class TestVCSOperations(BaseTestCase):
 
     @classmethod
@@ -207,7 +216,7 @@
     def test_clone_hg_repo_as_git(self):
         clone_url = _construct_url(HG_REPO)
         stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-        assert 'not found:' in stderr
+        assert 'not found' in stderr
 
     def test_clone_non_existing_path_hg(self):
         clone_url = _construct_url('trololo')
@@ -217,7 +226,7 @@
     def test_clone_non_existing_path_git(self):
         clone_url = _construct_url('trololo')
         stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-        assert 'not found:' in stderr
+        assert 'not found' in stderr
 
     def test_push_new_file_hg(self):
         DEST = _get_tmp_dir()
@@ -238,12 +247,15 @@
         # commit some stuff into this repo
         stdout, stderr = _add_files_and_push('git', DEST)
 
-        #WTF git stderr ?!
-        assert 'master -> master' in stderr
+        print [(x.repo_full_path,x.repo_path) for x in Repository.get_all()]
+        _check_proper_git_push(stdout, stderr)
 
     def test_push_invalidates_cache_hg(self):
         key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
-                                               ==HG_REPO).one()
+                                               ==HG_REPO).scalar()
+        if not key:
+            key = CacheInvalidation(HG_REPO, HG_REPO)
+
         key.cache_active = True
         Session().add(key)
         Session().commit()
@@ -253,13 +265,17 @@
         stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
 
         stdout, stderr = _add_files_and_push('hg', DEST, files_no=1)
+
         key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
                                                ==HG_REPO).one()
         self.assertEqual(key.cache_active, False)
 
     def test_push_invalidates_cache_git(self):
         key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
-                                               ==GIT_REPO).one()
+                                               ==GIT_REPO).scalar()
+        if not key:
+            key = CacheInvalidation(GIT_REPO, GIT_REPO)
+
         key.cache_active = True
         Session().add(key)
         Session().commit()
@@ -270,9 +286,11 @@
 
         # commit some stuff into this repo
         stdout, stderr = _add_files_and_push('git', DEST, files_no=1)
+        _check_proper_git_push(stdout, stderr)
 
         key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
                                                ==GIT_REPO).one()
+        print CacheInvalidation.get_all()
         self.assertEqual(key.cache_active, False)
 
     def test_push_wrong_credentials_hg(self):
@@ -313,7 +331,7 @@
         stdout, stderr = _add_files_and_push('git', DEST,
                                     clone_url='http://127.0.0.1:5000/tmp',)
 
-        assert 'not found:' in stderr
+        assert 'not found' in stderr
 
     def test_clone_and_create_lock_hg(self):
         # enable locking
@@ -386,31 +404,34 @@
                 % (HG_REPO, TEST_USER_ADMIN_LOGIN))
         assert msg in stderr
 
-#TODO: fix me ! somehow during tests hooks don't get called on GIT
-#    def test_push_on_locked_repo_by_other_user_git(self):
-#        #clone some temp
-#        DEST = _get_tmp_dir()
-#        clone_url = _construct_url(GIT_REPO, dest=DEST)
-#        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-#
-#        #lock repo
-#        r = Repository.get_by_repo_name(GIT_REPO)
-#        # let this user actually push !
-#        RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
-#                                          perm='repository.write')
-#        Session().commit()
-#        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
-#
-#        #push fails repo is locked by other user !
-#        stdout, stderr = _add_files_and_push('git', DEST,
-#                                             user=TEST_USER_REGULAR_LOGIN,
-#                                             passwd=TEST_USER_REGULAR_PASS)
-#        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
-#                % (GIT_REPO, TEST_USER_ADMIN_LOGIN))
-#        #TODO: fix this somehow later on GIT, GIT is stupid and even if we throw
-#        # back 423 to it, it makes ANOTHER request and we fail there with 405 :/
-#        msg = "405 Method Not Allowed"
-#        assert msg in stderr
+    def test_push_on_locked_repo_by_other_user_git(self):
+        #clone some temp
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        #lock repo
+        r = Repository.get_by_repo_name(GIT_REPO)
+        # let this user actually push !
+        RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
+                                          perm='repository.write')
+        Session().commit()
+        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
+
+        #push fails repo is locked by other user !
+        stdout, stderr = _add_files_and_push('git', DEST,
+                                             user=TEST_USER_REGULAR_LOGIN,
+                                             passwd=TEST_USER_REGULAR_PASS)
+        err = 'Repository `%s` locked by user `%s`' % (GIT_REPO, TEST_USER_ADMIN_LOGIN)
+        assert err in stderr
+
+        #TODO: fix this somehow later on GIT, GIT is stupid and even if we throw
+        #back 423 to it, it makes ANOTHER request and we fail there with 405 :/
+
+        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
+                % (GIT_REPO, TEST_USER_ADMIN_LOGIN))
+        #msg = "405 Method Not Allowed"
+        #assert msg in stderr
 
     def test_push_unlocks_repository_hg(self):
         # enable locking
@@ -425,7 +446,8 @@
 
         #check for lock repo after clone
         r = Repository.get_by_repo_name(HG_REPO)
-        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
+        uid = User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
+        assert r.locked[0] == uid
 
         #push is ok and repo is now unlocked
         stdout, stderr = _add_files_and_push('hg', DEST)
@@ -435,29 +457,31 @@
         r = Repository.get_by_repo_name(HG_REPO)
         assert r.locked == [None, None]
 
-#TODO: fix me ! somehow during tests hooks don't get called on GIT
-#    def test_push_unlocks_repository_git(self):
-#        # enable locking
-#        r = Repository.get_by_repo_name(GIT_REPO)
-#        r.enable_locking = True
-#        Session().add(r)
-#        Session().commit()
-#        #clone some temp
-#        DEST = _get_tmp_dir()
-#        clone_url = _construct_url(GIT_REPO, dest=DEST)
-#        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-#
-#        #check for lock repo after clone
-#        r = Repository.get_by_repo_name(GIT_REPO)
-#        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
-#
-#        #push is ok and repo is now unlocked
-#        stdout, stderr = _add_files_and_push('git', DEST)
-#        #assert ('remote: Released lock on repo `%s`' % GIT_REPO) in stdout
-#        #we need to cleanup the Session Here !
-#        Session.remove()
-#        r = Repository.get_by_repo_name(GIT_REPO)
-#        assert r.locked == [None, None]
+    #TODO: fix me ! somehow during tests hooks don't get called on GIT
+    def test_push_unlocks_repository_git(self):
+        # enable locking
+        r = Repository.get_by_repo_name(GIT_REPO)
+        r.enable_locking = True
+        Session().add(r)
+        Session().commit()
+        #clone some temp
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        #check for lock repo after clone
+        r = Repository.get_by_repo_name(GIT_REPO)
+        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
+
+        #push is ok and repo is now unlocked
+        stdout, stderr = _add_files_and_push('git', DEST)
+        _check_proper_git_push(stdout, stderr)
+
+        #assert ('remote: Released lock on repo `%s`' % GIT_REPO) in stdout
+        #we need to cleanup the Session Here !
+        Session.remove()
+        r = Repository.get_by_repo_name(GIT_REPO)
+        assert r.locked == [None, None]
 
     def test_ip_restriction_hg(self):
         user_model = UserModel()
--- a/rhodecode/tests/scripts/create_rc.sh	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/scripts/create_rc.sh	Wed Jul 02 19:03:13 2014 -0400
@@ -1,3 +1,4 @@
+#!/bin/sh
 psql -U postgres -h localhost -c 'drop database if exists rhodecode;'
 psql -U postgres -h localhost -c 'create database rhodecode;'
 paster setup-rhodecode rc.ini --force-yes --user=marcink --password=qweqwe --email=marcin@python-blog.com --repos=/home/marcink/repos --no-public-access
@@ -8,9 +9,9 @@
 rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo1 password:qweqwe email:demo1@rhodecode.org
 rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo2 password:qweqwe email:demo2@rhodecode.org
 rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo3 password:qweqwe email:demo3@rhodecode.org
-rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_users_group group_name:demo12
-rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_users_group usersgroupid:demo12 userid:demo1
-rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_users_group usersgroupid:demo12 userid:demo2
+rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user_group group_name:demo12
+rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_user_group usergroupid:demo12 userid:demo1
+rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_user_group usergroupid:demo12 userid:demo2
 echo "killing server"
 kill `cat rc.pid`
 rm rc.pid
--- a/rhodecode/tests/scripts/test_concurency.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/scripts/test_concurency.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.tests.test_hg_operations
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Test suite for making push/pull operations
-
-    :created_on: Dec 30, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,18 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.tests.test_hg_operations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Test suite for making push/pull operations
+
+:created_on: Dec 30, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+
+"""
 
 import os
 import sys
--- a/rhodecode/tests/scripts/test_crawler.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/scripts/test_crawler.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,19 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.tests.test_crawer
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Test for crawling a project for memory usage
-    This should be runned just as regular script together
-    with a watch script that will show memory usage.
-
-    watch -n1 ./rhodecode/tests/mem_watch
-
-    :created_on: Apr 21, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -26,6 +11,21 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.tests.test_crawer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Test for crawling a project for memory usage
+This should be runned just as regular script together
+with a watch script that will show memory usage.
+
+watch -n1 ./rhodecode/tests/mem_watch
+
+:created_on: Apr 21, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 
 import cookielib
--- a/rhodecode/tests/vcs/test_changesets.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/tests/vcs/test_changesets.py	Wed Jul 02 19:03:13 2014 -0400
@@ -135,6 +135,16 @@
         for tag, sha in self.repo.tags.iteritems():
             self.assertEqual(sha, self.repo.get_changeset(tag).raw_id)
 
+    def test_get_changeset_parents(self):
+        for test_rev in [1, 2, 3]:
+            sha = self.repo.get_changeset(test_rev-1)
+            self.assertEqual([sha], self.repo.get_changeset(test_rev).parents)
+
+    def test_get_changeset_children(self):
+        for test_rev in [1, 2, 3]:
+            sha = self.repo.get_changeset(test_rev+1)
+            self.assertEqual([sha], self.repo.get_changeset(test_rev).children)
+
 
 class ChangesetsTestCaseMixin(BackendTestMixin):
     recreate_repo_per_test = False
--- a/rhodecode/websetup.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/rhodecode/websetup.py	Wed Jul 02 19:03:13 2014 -0400
@@ -1,15 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-    rhodecode.websetup
-    ~~~~~~~~~~~~~~~~~~
-
-    Weboperations and setup for rhodecode
-
-    :created_on: Dec 11, 2010
-    :author: marcink
-    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -22,6 +11,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+rhodecode.websetup
+~~~~~~~~~~~~~~~~~~
+
+Weboperations and setup for rhodecode
+
+:created_on: Dec 11, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH.
+:license: GPLv3, see LICENSE for more details.
+"""
 
 import logging
 
--- a/setup.py	Wed Jul 02 19:03:10 2014 -0400
+++ b/setup.py	Wed Jul 02 19:03:13 2014 -0400
@@ -34,7 +34,7 @@
 is_windows = __platform__ in ['Windows']
 
 requirements = [
-    "waitress==0.8.4",
+    "waitress==0.8.8",
     "webob==1.0.8",
     "webtest==1.4.3",
     "Pylons==1.0.0",
@@ -42,38 +42,42 @@
     "WebHelpers==1.3",
     "formencode==1.2.4",
     "SQLAlchemy==0.7.10",
-    "Mako==0.7.3",
+    "Mako==0.9.0",
     "pygments>=1.5",
     "whoosh>=2.4.0,<2.5",
     "celery>=2.2.5,<2.3",
-    "babel",
+    "babel==0.9.6",
     "python-dateutil>=1.5.0,<2.0.0",
-    "dulwich>=0.8.7,<0.9.0",
+    "dulwich==0.9.3",
     "markdown==2.2.1",
     "docutils==0.8.1",
     "simplejson==2.5.2",
     "mock",
+    "pycrypto==2.6.0",
+    "URLObject==2.3.4",
+    "Routes==1.13",
 ]
 
 if sys.version_info < (2, 6):
     requirements.append("pysqlite")
 
 if sys.version_info < (2, 7):
+    requirements.append("importlib==1.0.1")
     requirements.append("unittest2")
     requirements.append("argparse")
 
 if is_windows:
-    requirements.append("mercurial==2.6.3")
+    requirements.append("mercurial==2.8.2")
 else:
-    requirements.append("py-bcrypt")
-    requirements.append("mercurial==2.6.3")
+    requirements.append("py-bcrypt==0.3.0")
+    requirements.append("mercurial==2.8.2")
 
 
 dependency_links = [
 ]
 
 classifiers = [
-    'Development Status :: 5 - Production/Stable',
+    'Development Status :: 4 - Beta'
     'Environment :: Web Environment',
     'Framework :: Pylons',
     'Intended Audience :: Developers',
@@ -166,7 +170,6 @@
 
     [paste.global_paster_command]
     setup-rhodecode=rhodecode.lib.paster_commands.setup_rhodecode:Command
-    cleanup-repos=rhodecode.lib.paster_commands.cleanup:Command
     update-repoinfo=rhodecode.lib.paster_commands.update_repoinfo:Command
     make-rcext=rhodecode.lib.paster_commands.make_rcextensions:Command
     repo-scan=rhodecode.lib.paster_commands.repo_scan:Command