changeset 2825:f7a52d548fd0

merge with beta
author Marcin Kuzminski <marcin@python-works.com>
date Fri, 07 Sep 2012 23:20:33 +0200
parents 4c13cedbde93 (current diff) 92822bd2e88a (diff)
children 909143a4dde5
files docs/changelog.rst docs/installation.rst docs/setup.rst docs/upgrade.rst rhodecode/__init__.py rhodecode/config/routing.py rhodecode/controllers/admin/settings.py rhodecode/controllers/changeset.py rhodecode/lib/celerylib/tasks.py rhodecode/lib/db_manage.py rhodecode/lib/hooks.py rhodecode/lib/utils.py rhodecode/model/db.py rhodecode/model/forms.py rhodecode/model/permission.py rhodecode/model/repo.py rhodecode/model/user.py rhodecode/templates/admin/settings/settings.html rhodecode/templates/admin/users/user_edit.html rhodecode/templates/summary/summary.html rhodecode/tests/functional/test_summary.py
diffstat 56 files changed, 1226 insertions(+), 221 deletions(-) [+]
line wrap: on
line diff
--- a/docs/changelog.rst	Mon Sep 03 22:22:58 2012 +0200
+++ b/docs/changelog.rst	Fri Sep 07 23:20:33 2012 +0200
@@ -4,6 +4,45 @@
 Changelog
 =========
 
+
+1.4.1 (**2012-09-07**)
+----------------------
+
+:status: in-progress
+:branch: beta
+
+news
+++++
+
+- always put a comment about code-review status change even if user send
+  empty data 
+- modified_on column saves repository update and it's going to be used
+  later for light version of main page ref #500
+- pull request notifications send much nicer emails with details about pull
+  request
+- #551 show breadcrumbs in summary view for repositories inside a group
+
+fixes
++++++
+
+- fixed migrations of permissions that can lead to inconsistency.
+  Some users sent feedback that after upgrading from older versions issues 
+  with updating default permissions occurred. RhodeCode detects that now and
+  resets default user permission to initial state if there is a need for that.
+  Also forces users to set the default value for new forking permission. 
+- #535 improved apache wsgi example configuration in docs
+- fixes #550 mercurial repositories comparision failed when origin repo had
+  additional not-common changesets
+- fixed status of code-review in preview windows of pull request
+- git forks were not initialized at bare repos
+- fixes #555 fixes issues with comparing non-related repositories
+- fixes #557 follower counter always counts up
+- fixed issue #560 require push ssl checkbox wasn't shown when option was
+  enabled
+- fixed #559
+- fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
+  if it was a request to url by repository ID
+
 1.4.0 (**2012-09-03**)
 ----------------------
 
--- a/docs/installation.rst	Mon Sep 03 22:22:58 2012 +0200
+++ b/docs/installation.rst	Fri Sep 07 23:20:33 2012 +0200
@@ -11,8 +11,8 @@
 **1.6**. If you're using older client, please upgrade.
 
 
-Installing RhodeCode from Cheese Shop
--------------------------------------
+Installing RhodeCode from PyPI (aka "Cheeseshop")
+-------------------------------------------------
 
 Rhodecode requires python version 2.5 or higher.
 
@@ -126,4 +126,4 @@
 .. _python: http://www.python.org/
 .. _mercurial: http://mercurial.selenic.com/
 .. _celery: http://celeryproject.org/
-.. _rabbitmq: http://www.rabbitmq.com/
\ No newline at end of file
+.. _rabbitmq: http://www.rabbitmq.com/
--- a/docs/setup.rst	Mon Sep 03 22:22:58 2012 +0200
+++ b/docs/setup.rst	Fri Sep 07 23:20:33 2012 +0200
@@ -667,12 +667,21 @@
 
 Here is a sample excerpt from an Apache Virtual Host configuration file::
 
-    WSGIDaemonProcess pylons user=www-data group=www-data processes=1 \
+    WSGIDaemonProcess pylons \
         threads=4 \
         python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages
     WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi
     WSGIPassAuthorization On
 
+.. note::
+   when running apache as root please add: `user=www-data group=www-data` 
+   into above configuration
+
+.. note::
+   RhodeCode cannot be runned in multiprocess mode in apache, make sure
+   you don't specify `processes=num` directive in the config
+
+
 Example wsgi dispatch script::
 
     import os
--- a/docs/upgrade.rst	Mon Sep 03 22:22:58 2012 +0200
+++ b/docs/upgrade.rst	Fri Sep 07 23:20:33 2012 +0200
@@ -4,14 +4,43 @@
 Upgrade
 =======
 
-Upgrading from Cheese Shop
---------------------------
+Upgrading from PyPI (aka "Cheeseshop")
+---------------------------------------
 
 .. note::
-   Firstly, it is recommended that you **always** perform a database backup 
-   before doing an upgrade.
+   Firstly, it is recommended that you **always** perform a database and 
+   configuration backup before doing an upgrade.
+   
+   (These directions will use '{version}' to note that this is the version of 
+   Rhodecode that these files were used with.  If backing up your RhodeCode 
+   instance from version 1.3.6 to 1.4.0, the ``production.ini`` file would be 
+   backed up to ``production.ini.1-3-6``.)
+
+
+If using a sqlite database, stop the Rhodecode process/daemon/service, and
+then make a copy of the database file::
+
+ service rhodecode stop
+ cp rhodecode.db rhodecode.db.{version}
+
 
-The easiest way to upgrade ``rhodecode`` is to run::
+Back up your configuration file::
+
+ cp production.ini production.ini.{version}
+
+
+Ensure that you are using the Python Virtual Environment that you'd originally
+installed Rhodecode in::
+
+ pip freeze
+
+will list all packages installed in the current environment.  If Rhodecode 
+isn't listed, change virtual environments to your venv location::
+
+ source /opt/rhodecode-venv/bin/activate
+
+
+Once you have verified the environment you can upgrade ``Rhodecode`` with::
 
  easy_install -U rhodecode
 
@@ -20,14 +49,13 @@
  pip install --upgrade rhodecode
 
 
-Then make sure you run the following command from the installation directory::
+Then run the following command from the installation directory::
  
  paster make-config RhodeCode production.ini
  
 This will display any changes made by the new version of RhodeCode to your
-current configuration. It will try to perform an automerge. It's always better
-to make a backup of your configuration file before hand and re check the 
-content after the automerge.
+current configuration. It will try to perform an automerge. It's recommended 
+that you re-check the content after the automerge.
 
 .. note::
    Please always make sure your .ini files are up to date. Often errors are
@@ -41,12 +69,25 @@
 
 The final step is to upgrade the database. To do this simply run::
 
-    paster upgrade-db production.ini
+ paster upgrade-db production.ini
  
 This will upgrade the schema and update some of the defaults in the database,
 and will always recheck the settings of the application, if there are no new 
 options that need to be set.
 
+You may find it helpful to clear out your log file so that new errors are 
+readily apparent::
+
+ echo > rhodecode.log
+
+Once that is complete, you may now start your upgraded Rhodecode Instance::
+
+ service rhodecode start
+
+Or::
+
+ paster serve /var/www/rhodecode/production.ini
+
 .. note::
    If you're using Celery, make sure you restart all instances of it after
    upgrade.
@@ -55,4 +96,4 @@
 .. _python: http://www.python.org/
 .. _mercurial: http://mercurial.selenic.com/
 .. _celery: http://celeryproject.org/
-.. _rabbitmq: http://www.rabbitmq.com/
\ No newline at end of file
+.. _rabbitmq: http://www.rabbitmq.com/
--- a/rhodecode/__init__.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/__init__.py	Fri Sep 07 23:20:33 2012 +0200
@@ -26,7 +26,7 @@
 import sys
 import platform
 
-VERSION = (1, 4, 0)
+VERSION = (1, 4, 1)
 
 try:
     from rhodecode.lib import get_current_revision
@@ -38,7 +38,7 @@
 
 __version__ = ('.'.join((str(each) for each in VERSION[:3])) +
                '.'.join(VERSION[3:]))
-__dbversion__ = 6  # defines current db version for migrations
+__dbversion__ = 7  # defines current db version for migrations
 __platform__ = platform.system()
 __license__ = 'GPLv3'
 __py_version__ = sys.version_info
--- a/rhodecode/config/routing.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/config/routing.py	Fri Sep 07 23:20:33 2012 +0200
@@ -34,7 +34,7 @@
 
         try:
             by_id = repo_name.split('_')
-            if len(by_id) == 2 and by_id[1].isdigit():
+            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:
--- a/rhodecode/controllers/admin/repos_groups.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/controllers/admin/repos_groups.py	Fri Sep 07 23:20:33 2012 +0200
@@ -45,6 +45,7 @@
 from rhodecode.model.meta import Session
 from rhodecode.model.repo import RepoModel
 from webob.exc import HTTPInternalServerError, HTTPNotFound
+from rhodecode.lib.utils2 import str2bool
 
 log = logging.getLogger(__name__)
 
@@ -162,7 +163,7 @@
             Session().commit()
             h.flash(_('updated repos group %s') \
                     % form_result['group_name'], category='success')
-            #TODO: in futureaction_logger(, '', '', '', self.sa)
+            #TODO: in future action_logger(, '', '', '', self.sa)
         except formencode.Invalid, errors:
 
             return htmlfill.render(
@@ -227,10 +228,11 @@
 
         :param group_name:
         """
-
         try:
-            ReposGroupModel().revoke_user_permission(
-                repos_group=group_name, user=request.POST['user_id']
+            recursive = str2bool(request.POST.get('recursive', False))
+            ReposGroupModel().delete_permission(
+                repos_group=group_name, obj=request.POST['user_id'],
+                obj_type='user', recursive=recursive
             )
             Session().commit()
         except Exception:
@@ -248,9 +250,10 @@
         """
 
         try:
-            ReposGroupModel().revoke_users_group_permission(
-                repos_group=group_name,
-                group_name=request.POST['users_group_id']
+            recursive = str2bool(request.POST.get('recursive', False))
+            ReposGroupModel().delete_permission(
+                repos_group=group_name, obj=request.POST['users_group_id'],
+                obj_type='users_group', recursive=recursive
             )
             Session().commit()
         except Exception:
--- a/rhodecode/controllers/admin/settings.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/controllers/admin/settings.py	Fri Sep 07 23:20:33 2012 +0200
@@ -51,6 +51,7 @@
 from rhodecode.model.db import User
 from rhodecode.model.notification import EmailNotificationModel
 from rhodecode.model.meta import Session
+from rhodecode.lib.utils2 import str2bool
 
 log = logging.getLogger(__name__)
 
@@ -471,6 +472,9 @@
             if k == '/':
                 k = 'root_path'
 
+            if k == 'push_ssl':
+                v = str2bool(v)
+
             if k.find('.') != -1:
                 k = k.replace('.', '_')
 
@@ -478,5 +482,4 @@
                 v = each.ui_active
 
             settings[each.ui_section + '_' + k] = v
-
         return settings
--- a/rhodecode/controllers/changeset.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/controllers/changeset.py	Fri Sep 07 23:20:33 2012 +0200
@@ -376,9 +376,13 @@
     def comment(self, repo_name, revision):
         status = request.POST.get('changeset_status')
         change_status = request.POST.get('change_changeset_status')
+        text = request.POST.get('text')
+        if status and change_status:
+            text = text or (_('Status change -> %s')
+                            % ChangesetStatus.get_status_lbl(status))
 
         comm = ChangesetCommentsModel().create(
-            text=request.POST.get('text'),
+            text=text,
             repo=c.rhodecode_db_repo.repo_id,
             user=c.rhodecode_user.user_id,
             revision=revision,
@@ -391,7 +395,7 @@
         # get status if set !
         if status and change_status:
             # if latest status was from pull request and it's closed
-            # disallow changing status ! 
+            # disallow changing status !
             # dont_allow_on_closed_pull_request = True !
 
             try:
--- a/rhodecode/controllers/pullrequests.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/controllers/pullrequests.py	Fri Sep 07 23:20:33 2012 +0200
@@ -249,8 +249,7 @@
                                        org_repo, org_ref, other_repo, other_ref
                                       )
 
-        c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
-                                                   c.cs_ranges])
+        c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
         # defines that we need hidden inputs with changesets
         c.as_form = request.GET.get('as_form', False)
 
@@ -277,6 +276,7 @@
         c.users_array = repo_model.get_users_js()
         c.users_groups_array = repo_model.get_users_groups_js()
         c.pull_request = PullRequest.get_or_404(pull_request_id)
+        c.target_repo = c.pull_request.org_repo.repo_name
 
         cc_model = ChangesetCommentsModel()
         cs_model = ChangesetStatusModel()
@@ -322,12 +322,20 @@
         c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
                                            pull_request=pull_request_id)
 
-        # changeset(pull-request) status
-        c.current_changeset_status = cs_model.calculate_status(
-                                        c.pull_request_reviewers
-                                     )
+        try:
+            cur_status = c.statuses[c.pull_request.revisions[0]][0]
+        except:
+            log.error(traceback.format_exc())
+            cur_status = 'undefined'
+        if c.pull_request.is_closed() and 0:
+            c.current_changeset_status = cur_status
+        else:
+            # changeset(pull-request) status calulation based on reviewers
+            c.current_changeset_status = cs_model.calculate_status(
+                                            c.pull_request_reviewers,
+                                         )
         c.changeset_statuses = ChangesetStatus.STATUSES
-        c.target_repo = c.pull_request.org_repo.repo_name
+
         return render('/pullrequests/pullrequest_show.html')
 
     @NotAnonymous()
@@ -339,9 +347,12 @@
 
         status = request.POST.get('changeset_status')
         change_status = request.POST.get('change_changeset_status')
-
+        text = request.POST.get('text')
+        if status and change_status:
+            text = text or (_('Status change -> %s')
+                            % ChangesetStatus.get_status_lbl(status))
         comm = ChangesetCommentsModel().create(
-            text=request.POST.get('text'),
+            text=text,
             repo=c.rhodecode_db_repo.repo_id,
             user=c.rhodecode_user.user_id,
             pull_request=pull_request_id,
--- a/rhodecode/lib/celerylib/tasks.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/lib/celerylib/tasks.py	Fri Sep 07 23:20:33 2012 +0200
@@ -400,9 +400,19 @@
     log.info('creating fork of %s as %s', source_repo_path,
              destination_fork_path)
     backend = get_backend(repo_type)
-    backend(safe_str(destination_fork_path), create=True,
-            src_url=safe_str(source_repo_path),
-            update_after_clone=update_after_clone)
+
+    if repo_type == 'git':
+        backend(safe_str(destination_fork_path), create=True,
+                src_url=safe_str(source_repo_path),
+                update_after_clone=update_after_clone,
+                bare=True)
+    elif repo_type == 'hg':
+        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)
+
     log_create_repository(fork_repo.get_dict(), created_by=cur_user.username)
 
     action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
--- a/rhodecode/lib/compat.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/lib/compat.py	Fri Sep 07 23:20:33 2012 +0200
@@ -589,6 +589,3 @@
                     self.__cond.wait(timeout)
             finally:
                 self.__cond.release()
-
-
-
--- a/rhodecode/lib/db_manage.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/lib/db_manage.py	Fri Sep 07 23:20:33 2012 +0200
@@ -247,7 +247,22 @@
                 Session().add(hggit)
 
                 notify('re-check default permissions')
-                self.klass.populate_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')
 
         upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
 
@@ -470,6 +485,28 @@
                 log.debug('missing default permission for group %s adding' % g)
                 ReposGroupModel()._create_default_perms(g)
 
+    def reset_permissions(self, username):
+        """
+        Resets permissions to default state, usefull when old systems had
+        bad permissions, we must clean them up
+
+        :param username:
+        :type username:
+        """
+        default_user = User.get_by_username(username)
+        if not default_user:
+            return
+
+        u2p = UserToPerm.query()\
+            .filter(UserToPerm.user == default_user).all()
+        fixed = False
+        if len(u2p) != len(User.DEFAULT_PERMISSIONS):
+            for p in u2p:
+                Session().delete(p)
+            fixed = True
+            self.populate_default_permissions()
+        return fixed
+
     def config_prompt(self, test_repo_path='', retries=3, defaults={}):
         _path = defaults.get('repos_location')
         if retries == 3:
@@ -506,7 +543,15 @@
             retries -= 1
             return self.config_prompt(test_repo_path, retries)
 
-        return path
+        real_path = os.path.realpath(path)
+
+        if real_path != path:
+            if not ask_ok(('Path looks like a symlink, Rhodecode will store '
+                           'given path as %s ? [y/n]') % (real_path)):
+                log.error('Canceled by user')
+                sys.exit(-1)
+
+        return real_path
 
     def create_settings(self, path):
 
@@ -597,8 +642,7 @@
 
         default_user = User.get_by_username('default')
 
-        for def_perm in ['hg.register.manual_activate', 'hg.create.repository',
-                         'hg.fork.repository', 'repository.read']:
+        for def_perm in User.DEFAULT_PERMISSIONS:
 
             perm = self.sa.query(Permission)\
              .filter(Permission.permission_name == def_perm)\
--- a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py	Fri Sep 07 23:20:33 2012 +0200
@@ -1317,4 +1317,4 @@
     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)
\ No newline at end of file
+    other_ref = Column('other_ref', Unicode(256), nullable=False)
--- a/rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py	Fri Sep 07 23:20:33 2012 +0200
@@ -49,7 +49,7 @@
     tbl = ChangesetStatus.__table__
     tbl.create()
 
-    ## RESET COMPLETLY THE metadata for sqlalchemy to use the 1_3_0 Base 
+    ## RESET COMPLETLY THE metadata for sqlalchemy to use the 1_3_0 Base
     Base = declarative_base()
     Base.metadata.clear()
     Base.metadata = MetaData()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/007_version_1_4_0.py	Fri Sep 07 23:20:33 2012 +0200
@@ -0,0 +1,51 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper
+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
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+
+    #==========================================================================
+    # CHANGESET_COMMENTS
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_4_0 import ChangesetComment
+    tbl_name = ChangesetComment.__tablename__
+    tbl = Table(tbl_name,
+                MetaData(bind=migrate_engine), autoload=True,
+                autoload_with=migrate_engine)
+    col = tbl.columns.revision
+
+    # remove nullability from revision field
+    col.alter(nullable=True)
+
+    #==========================================================================
+    # REPOSITORY
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_4_0 import Repository
+    tbl = Repository.__table__
+    updated_on = Column('updated_on', DateTime(timezone=False),
+                        nullable=True, unique=None)
+    # create created on column for future lightweight main page
+    updated_on.create(table=tbl)
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
--- a/rhodecode/lib/diffs.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/lib/diffs.py	Fri Sep 07 23:20:33 2012 +0200
@@ -610,7 +610,7 @@
                 other_repo.ui.setconfig('hooks', k, None)
 
             unbundle = other_repo.getbundle('incoming', common=common,
-                                            heads=rheads)
+                                            heads=None)
 
             buf = BytesIO()
             while True:
--- a/rhodecode/lib/ext_json.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/lib/ext_json.py	Fri Sep 07 23:20:33 2012 +0200
@@ -92,7 +92,7 @@
                 return _obj_dump(obj)
             except NotImplementedError:
                 pass
-            return json.JSONEncoder.default(self, obj)
+            raise TypeError("%r is not JSON serializable" % (obj,))
     # monkey-patch JSON encoder to use extended version
     json.dumps = functools.partial(json.dumps, cls=ExtendedEncoder)
     json.dump = functools.partial(json.dump, cls=ExtendedEncoder)
--- a/rhodecode/lib/hooks.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/lib/hooks.py	Fri Sep 07 23:20:33 2012 +0200
@@ -34,10 +34,9 @@
 from rhodecode.lib.utils import action_logger
 from rhodecode.lib.vcs.backends.base import EmptyChangeset
 from rhodecode.lib.compat import json
-from rhodecode.model.db import Repository, User
+from rhodecode.lib.exceptions import HTTPLockedRC
 from rhodecode.lib.utils2 import safe_str
-from rhodecode.lib.exceptions import HTTPLockedRC
-
+from rhodecode.model.db import Repository, User
 
 def _get_scm_size(alias, root_path):
 
@@ -330,7 +329,12 @@
     # fix if it's not a bare repo
     if repo_path.endswith('.git'):
         repo_path = repo_path[:-4]
+
     repo = Repository.get_by_full_path(repo_path)
+    if not repo:
+        raise OSError('Repository %s not found in database'
+                      % (safe_str(repo_path)))
+
     _hooks = dict(baseui.configitems('hooks')) or {}
 
     extras = json.loads(env['RHODECODE_EXTRAS'])
--- a/rhodecode/lib/utils.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/lib/utils.py	Fri Sep 07 23:20:33 2012 +0200
@@ -206,7 +206,7 @@
 def is_valid_repo(repo_name, base_path, scm=None):
     """
     Returns True if given path is a valid repository False otherwise.
-    If scm param is given also compare if given scm is the same as expected 
+    If scm param is given also compare if given scm is the same as expected
     from scm parameter
 
     :param repo_name:
@@ -413,6 +413,11 @@
         raise Exception('Missing administrative account !')
     added = []
 
+#    # clear cache keys
+#    log.debug("Clearing cache keys now...")
+#    CacheInvalidation.clear_cache()
+#    sa.commit()
+
     for name, repo in initial_repo_list.items():
         group = map_groups(name)
         db_repo = rm.get_by_repo_name(name)
@@ -438,6 +443,11 @@
         elif install_git_hook:
             if db_repo.repo_type == 'git':
                 ScmModel().install_git_hook(db_repo.scm_instance)
+        # during starting install all cache keys for all repositories in the
+        # system, this will register all repos and multiple instances
+        key, _prefix, _org_key = CacheInvalidation._get_key(name)
+        log.debug("Creating cache key for %s instance_id:`%s`" % (name, _prefix))
+        CacheInvalidation._get_or_create_key(key, _prefix, _org_key, commit=False)
     sa.commit()
     removed = []
     if remove_obsolete:
@@ -455,10 +465,6 @@
                     log.error(traceback.format_exc())
                     sa.rollback()
 
-    # clear cache keys
-    log.debug("Clearing cache keys now...")
-    CacheInvalidation.clear_cache()
-    sa.commit()
     return added, removed
 
 
--- a/rhodecode/lib/vcs/backends/git/repository.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/lib/vcs/backends/git/repository.py	Fri Sep 07 23:20:33 2012 +0200
@@ -178,7 +178,7 @@
             raise urllib2.URLError("[%s] %s" % (url, e))
 
     def _get_repo(self, create, src_url=None, update_after_clone=False,
-            bare=False):
+                  bare=False):
         if create and os.path.exists(self.path):
             raise RepositoryError("Location already exist")
         if src_url and not create:
--- a/rhodecode/lib/vcs/utils/hgcompat.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/lib/vcs/utils/hgcompat.py	Fri Sep 07 23:20:33 2012 +0200
@@ -14,4 +14,5 @@
 from mercurial.encoding import tolocal
 from mercurial import discovery
 from mercurial import localrepo
-from mercurial import scmutil
\ No newline at end of file
+from mercurial import scmutil
+from mercurial.discovery import findcommonoutgoing
--- a/rhodecode/model/changeset_status.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/model/changeset_status.py	Fri Sep 07 23:20:33 2012 +0200
@@ -64,7 +64,7 @@
 
     def calculate_status(self, statuses_by_reviewers):
         """
-        leading one wins, if number of occurences are equal than weaker wins
+        leading one wins, if number of occurrences are equal than weaker wins
 
         :param statuses_by_reviewers:
         """
--- a/rhodecode/model/comment.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/model/comment.py	Fri Sep 07 23:20:33 2012 +0200
@@ -123,18 +123,20 @@
             recipients = ChangesetComment.get_users(revision=revision)
             # add changeset author if it's in rhodecode system
             recipients += [User.get_by_email(author_email)]
+            email_kwargs = {
+                'status_change': status_change,
+            }
         #pull request
         elif pull_request:
+            _url = h.url('pullrequest_show',
+                repo_name=pull_request.other_repo.repo_name,
+                pull_request_id=pull_request.pull_request_id,
+                anchor='comment-%s' % comment.comment_id,
+                qualified=True,
+            )
             subj = safe_unicode(
                 h.link_to('Re pull request: %(desc)s %(line)s' % \
-                          {'desc': desc, 'line': line},
-                          h.url('pullrequest_show',
-                                repo_name=pull_request.other_repo.repo_name,
-                                pull_request_id=pull_request.pull_request_id,
-                                anchor='comment-%s' % comment.comment_id,
-                                qualified=True,
-                          )
-                )
+                          {'desc': desc, 'line': line}, _url)
             )
 
             notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
@@ -144,22 +146,36 @@
             # add pull request author
             recipients += [pull_request.author]
 
+            # add the reviewers to notification
+            recipients += [x.user for x in pull_request.reviewers]
+
+            #set some variables for email notification
+            email_kwargs = {
+                'pr_id': pull_request.pull_request_id,
+                'status_change': status_change,
+                'pr_comment_url': _url,
+                'pr_comment_user': h.person(user.email),
+                'pr_target_repo': h.url('summary_home',
+                                   repo_name=pull_request.other_repo.repo_name,
+                                   qualified=True)
+            }
         # create notification objects, and emails
         NotificationModel().create(
-          created_by=user, subject=subj, body=body,
-          recipients=recipients, type_=notification_type,
-          email_kwargs={'status_change': status_change}
+            created_by=user, subject=subj, body=body,
+            recipients=recipients, type_=notification_type,
+            email_kwargs=email_kwargs
         )
 
         mention_recipients = set(self._extract_mentions(body))\
                                 .difference(recipients)
         if mention_recipients:
+            email_kwargs.update({'pr_mention': True})
             subj = _('[Mention]') + ' ' + subj
             NotificationModel().create(
                 created_by=user, subject=subj, body=body,
                 recipients=mention_recipients,
                 type_=notification_type,
-                email_kwargs={'status_change': status_change}
+                email_kwargs=email_kwargs
             )
 
         return comment
--- a/rhodecode/model/db.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/model/db.py	Fri Sep 07 23:20:33 2012 +0200
@@ -278,6 +278,10 @@
 
         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'
@@ -289,7 +293,10 @@
          'mysql_charset': 'utf8'}
     )
     DEFAULT_USER = 'default'
-
+    DEFAULT_PERMISSIONS = [
+        'hg.register.manual_activate', 'hg.create.repository',
+        'hg.fork.repository', 'repository.read'
+    ]
     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)
@@ -602,6 +609,7 @@
     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)
@@ -742,6 +750,16 @@
         p += self.repo_name.split(Repository.url_sep())
         return os.path.join(*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
@@ -1398,6 +1416,13 @@
         return u"<%s('%s:%s')>" % (self.__class__.__name__,
                                   self.cache_id, self.cache_key)
 
+    @property
+    def prefix(self):
+        _split = self.cache_key.split(self.cache_args, 1)
+        if _split and len(_split) == 2:
+            return _split[0]
+        return ''
+
     @classmethod
     def clear_cache(cls):
         cls.query().delete()
@@ -1421,13 +1446,14 @@
         return cls.query().filter(cls.cache_key == key).scalar()
 
     @classmethod
-    def _get_or_create_key(cls, key, prefix, org_key):
+    def _get_or_create_key(cls, key, prefix, org_key, commit=True):
         inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
         if not inv_obj:
             try:
                 inv_obj = CacheInvalidation(key, org_key)
                 Session().add(inv_obj)
-                Session().commit()
+                if commit:
+                    Session().commit()
             except Exception:
                 log.error(traceback.format_exc())
                 Session().rollback()
--- a/rhodecode/model/forms.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/model/forms.py	Fri Sep 07 23:20:33 2012 +0200
@@ -128,6 +128,7 @@
                                         testValueList=True,
                                         if_missing=None, not_empty=False)
         enable_locking = v.StringBoolean(if_missing=False)
+        recursive = v.StringBoolean(if_missing=False)
         chained_validators = [v.ValidReposGroup(edit, old_data),
                               v.ValidPerms('group')]
 
@@ -340,4 +341,4 @@
         pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
         pullrequest_desc = v.UnicodeString(strip=True, required=False)
 
-    return _PullRequestForm
\ No newline at end of file
+    return _PullRequestForm
--- a/rhodecode/model/notification.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/model/notification.py	Fri Sep 07 23:20:33 2012 +0200
@@ -245,6 +245,7 @@
     TYPE_PASSWORD_RESET = 'passoword_link'
     TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
     TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
+    TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
     TYPE_DEFAULT = 'default'
 
     def __init__(self):
@@ -255,7 +256,9 @@
          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_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	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/model/permission.py	Fri Sep 07 23:20:33 2012 +0200
@@ -77,7 +77,7 @@
                                 form_result['perm_user_name']).scalar()
         u2p = self.sa.query(UserToPerm).filter(UserToPerm.user ==
                                                perm_user).all()
-        if len(u2p) != 4:
+        if len(u2p) != len(User.DEFAULT_PERMISSIONS):
             raise Exception('Defined: %s should be 4  permissions for default'
                             ' user. This should not happen please verify'
                             ' your database' % len(u2p))
--- a/rhodecode/model/pull_request.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/model/pull_request.py	Fri Sep 07 23:20:33 2012 +0200
@@ -36,7 +36,8 @@
 from rhodecode.model.notification import NotificationModel
 from rhodecode.lib.utils2 import safe_unicode
 
-from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil
+from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil, \
+    findcommonoutgoing
 
 log = logging.getLogger(__name__)
 
@@ -79,22 +80,30 @@
         #notification to reviewers
         notif = NotificationModel()
 
+        pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
+                       pull_request_id=new.pull_request_id,
+                       qualified=True,
+        )
         subject = safe_unicode(
             h.link_to(
               _('%(user)s wants you to review pull request #%(pr_id)s') % \
                 {'user': created_by_user.username,
                  'pr_id': new.pull_request_id},
-              h.url('pullrequest_show', repo_name=other_repo.repo_name,
-                    pull_request_id=new.pull_request_id,
-                    qualified=True,
-              )
+                pr_url
             )
         )
         body = description
+        kwargs = {
+            'pr_title': title,
+            'pr_user_created': h.person(created_by_user.email),
+            'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
+                                 qualified=True,),
+            'pr_url': pr_url,
+            'pr_revisions': revisions
+        }
         notif.create(created_by=created_by_user, subject=subject, body=body,
                      recipients=reviewers,
-                     type_=Notification.TYPE_PULL_REQUEST,)
-
+                     type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
         return new
 
     def update_reviewers(self, pull_request, reviewers_ids):
@@ -156,7 +165,10 @@
         #case two independent repos
         common, incoming, rheads = discovery_data
         if org_repo != other_repo and incoming:
-            revs = org_repo._repo.changelog.findmissing(common, rheads)
+            obj = findcommonoutgoing(org_repo._repo,
+                        localrepo.locallegacypeer(other_repo._repo.local()),
+                        force=True)
+            revs = obj.missing
 
             for cs in reversed(map(binascii.hexlify, revs)):
                 changesets.append(org_repo.get_changeset(cs))
@@ -205,6 +217,7 @@
         log.debug('Doing discovery for %s@%s vs %s@%s' % (
                         org_repo, org_ref, other_repo, other_ref)
         )
+
         #log.debug('Filter heads are %s[%s]' % ('', org_ref[1]))
         org_peer = localrepo.locallegacypeer(_org_repo.local())
         tmp = discovery.findcommonincoming(
@@ -212,7 +225,7 @@
                   remote=org_peer,  # org_repo source for incoming
                   heads=[_other_repo[other_rev].node(),
                          _org_repo[org_rev].node()],
-                  force=False
+                  force=True
         )
         return tmp
 
--- a/rhodecode/model/repo.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/model/repo.py	Fri Sep 07 23:20:33 2012 +0200
@@ -368,6 +368,7 @@
         obj.user = user
         obj.permission = permission
         self.sa.add(obj)
+        log.debug('Granted perm %s to %s on %s' % (perm, user, repo))
 
     def revoke_user_permission(self, repo, user):
         """
@@ -383,8 +384,10 @@
         obj = self.sa.query(UserRepoToPerm)\
             .filter(UserRepoToPerm.repository == repo)\
             .filter(UserRepoToPerm.user == user)\
-            .one()
-        self.sa.delete(obj)
+            .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):
         """
@@ -414,6 +417,7 @@
         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))
 
     def revoke_users_group_permission(self, repo, group_name):
         """
@@ -429,8 +433,10 @@
         obj = self.sa.query(UsersGroupRepoToPerm)\
             .filter(UsersGroupRepoToPerm.repository == repo)\
             .filter(UsersGroupRepoToPerm.users_group == group_name)\
-            .one()
-        self.sa.delete(obj)
+            .scalar()
+        if obj:
+            self.sa.delete(obj)
+            log.debug('Revoked perm to %s on %s' % (repo, group_name))
 
     def delete_stats(self, repo_name):
         """
--- a/rhodecode/model/repos_group.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/model/repos_group.py	Fri Sep 07 23:20:33 2012 +0200
@@ -32,7 +32,7 @@
 
 from rhodecode.model import BaseModel
 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
-    User, Permission, UsersGroupRepoGroupToPerm, UsersGroup
+    User, Permission, UsersGroupRepoGroupToPerm, UsersGroup, Repository
 
 log = logging.getLogger(__name__)
 
@@ -115,11 +115,12 @@
                             'existing dir %s' % new_path)
         shutil.move(old_path, new_path)
 
-    def __delete_group(self, group):
+    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)
@@ -127,7 +128,10 @@
         rm_path = os.path.join(self.repos_path, paths)
         if os.path.isdir(rm_path):
             # delete only if that path really exists
-            os.rmdir(rm_path)
+            if force_delete:
+                shutil.rmtree(rm_path)
+            else:
+                os.rmdir(rm_path)  # this raises an exception when there are still objects inside
 
     def create(self, group_name, group_description, parent=None, just_db=False):
         try:
@@ -150,32 +154,79 @@
             log.error(traceback.format_exc())
             raise
 
+    def _update_permissions(self, repos_group, perms_new=None,
+                            perms_updates=None, recursive=False):
+        from rhodecode.model.repo import RepoModel
+        if not perms_new:
+            perms_new = []
+        if not perms_updates:
+            perms_updates = []
+
+        def _set_perm_user(obj, user, perm):
+            if isinstance(obj, RepoGroup):
+                ReposGroupModel().grant_user_permission(
+                    repos_group=obj, user=user, 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_permission(
+                    repo=obj, user=user, perm=perm
+                )
+
+        def _set_perm_group(obj, users_group, perm):
+            if isinstance(obj, RepoGroup):
+                ReposGroupModel().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
+                )
+        updates = []
+        log.debug('Now updating permissions for %s in recursive mode:%s'
+                  % (repos_group, recursive))
+
+        for obj in repos_group.recursive_groups_and_repos():
+            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 users group
+                else:
+                    _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:
+                    _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_id, form_data):
 
         try:
             repos_group = RepoGroup.get(repos_group_id)
-
-            # update permissions
-            for member, perm, member_type in form_data['perms_updates']:
-                if member_type == 'user':
-                    # this updates also current one if found
-                    ReposGroupModel().grant_user_permission(
-                        repos_group=repos_group, user=member, perm=perm
-                    )
-                else:
-                    ReposGroupModel().grant_users_group_permission(
-                        repos_group=repos_group, group_name=member, perm=perm
-                    )
-            # set new permissions
-            for member, perm, member_type in form_data['perms_new']:
-                if member_type == 'user':
-                    ReposGroupModel().grant_user_permission(
-                        repos_group=repos_group, user=member, perm=perm
-                    )
-                else:
-                    ReposGroupModel().grant_users_group_permission(
-                        repos_group=repos_group, group_name=member, perm=perm
-                    )
+            recursive = form_data['recursive']
+            # iterate over all members(if in recursive mode) of this groups and
+            # set the permissions !
+            # this can be potentially heavy operation
+            self._update_permissions(repos_group, form_data['perms_new'],
+                                     form_data['perms_updates'], recursive)
 
             old_path = repos_group.full_path
 
@@ -191,7 +242,6 @@
 
             # iterate over all members of this groups and set the locking !
             # 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
@@ -210,15 +260,54 @@
             log.error(traceback.format_exc())
             raise
 
-    def delete(self, repos_group):
+    def delete(self, repos_group, force_delete=False):
         repos_group = self._get_repos_group(repos_group)
         try:
             self.sa.delete(repos_group)
-            self.__delete_group(repos_group)
+            self.__delete_group(repos_group, force_delete)
         except:
             log.exception('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 users group
+
+        :param repos_group:
+        :param obj: user or users group id
+        :param obj_type: user or users group type
+        :param recursive: recurse to all children of group
+        """
+        from rhodecode.model.repo import RepoModel
+        repos_group = self._get_repos_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 repositories group, or update
@@ -246,6 +335,7 @@
         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):
         """
@@ -262,8 +352,10 @@
         obj = self.sa.query(UserRepoGroupToPerm)\
             .filter(UserRepoGroupToPerm.user == user)\
             .filter(UserRepoGroupToPerm.group == repos_group)\
-            .one()
-        self.sa.delete(obj)
+            .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):
         """
@@ -294,6 +386,7 @@
         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):
         """
@@ -310,5 +403,7 @@
         obj = self.sa.query(UsersGroupRepoGroupToPerm)\
             .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
             .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
-            .one()
-        self.sa.delete(obj)
+            .scalar()
+        if obj:
+            self.sa.delete(obj)
+            log.debug('Revoked perm to %s on %s' % (repos_group, group_name))
--- a/rhodecode/model/user.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/model/user.py	Fri Sep 07 23:20:33 2012 +0200
@@ -564,7 +564,7 @@
             rg_k = perm.UserRepoGroupToPerm.group.group_name
             p = perm.Permission.permission_name
             cur_perm = user.permissions[GK][rg_k]
-            if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
+            if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm] or 1:  # disable check
                 user.permissions[GK][rg_k] = p
 
         # REPO GROUP + USER GROUP
@@ -588,7 +588,7 @@
             cur_perm = user.permissions[GK][g_k]
             # overwrite permission only if it's greater than permission
             # given from other sources
-            if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
+            if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm] or 1:  # disable check
                 user.permissions[GK][g_k] = p
 
         return user
--- a/rhodecode/model/validators.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/model/validators.py	Fri Sep 07 23:20:33 2012 +0200
@@ -499,9 +499,9 @@
             # fill new permissions in order of how they were added
             for k in sorted(map(int, new_perms_group.keys())):
                 perm_dict = new_perms_group[str(k)]
-                new_member = perm_dict['name']
-                new_perm = perm_dict['perm']
-                new_type = perm_dict['type']
+                new_member = perm_dict.get('name')
+                new_perm = perm_dict.get('perm')
+                new_type = perm_dict.get('type')
                 if new_member and new_perm and new_type:
                     perms_new.add((new_member, new_perm, new_type))
 
--- a/rhodecode/templates/admin/repos/repo_edit.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/admin/repos/repo_edit.html	Fri Sep 07 23:20:33 2012 +0200
@@ -115,7 +115,7 @@
                     ${h.checkbox('enable_locking',value="True")}
                     <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
                 </div>
-            </div>            
+            </div>
             <div class="field">
                 <div class="label">
                     <label for="user">${_('Owner')}:</label>
@@ -188,6 +188,20 @@
         <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')}
+                  <ul>
+                  %for cache in c.repo_info.cache_keys:
+                      <li>INSTANCE ID:${cache.prefix or '-'} ${cache.cache_args} CACHED: ${h.bool2icon(cache.cache_active)}</li>
+                  %endfor
+                  </ul>
+              </div>
            </div>
         </div>
         ${h.end_form()}
@@ -195,20 +209,20 @@
         <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>
+          ${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()}
 
@@ -229,7 +243,7 @@
                 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
                 </li>
            </ul>
-           </div>           
+           </div>
         </div>
         ${h.end_form()}
 
@@ -245,9 +259,9 @@
                     <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
                </ul>
                </div>
-        </div>        
+        </div>
         ${h.end_form()}
-        
+
         <h3>${_('Delete')}</h3>
         ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
         <div class="form">
@@ -262,7 +276,7 @@
            </ul>
            </div>
         </div>
-        ${h.end_form()}        
+        ${h.end_form()}
 </div>
 
 </%def>
--- a/rhodecode/templates/admin/repos/repo_edit_perms.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/admin/repos/repo_edit_perms.html	Fri Sep 07 23:20:33 2012 +0200
@@ -74,8 +74,8 @@
             </div> \
         </td> \
         <td></td>'""")
-    %>    
-    ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'    
+    %>
+    ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
     <tr class="new_members last_new_member" id="add_perm_input"></tr>
     <tr>
         <td colspan="6">
--- a/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html	Fri Sep 07 23:20:33 2012 +0200
@@ -58,8 +58,8 @@
             </div> \
         </td> \
         <td></td>'""")
-    %>    
-    ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'    
+    %>
+    ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
     <tr class="new_members last_new_member" id="add_perm_input"></tr>
     <tr>
         <td colspan="6">
@@ -68,6 +68,12 @@
             </span>
         </td>
     </tr>
+    <tr>
+        <td colspan="6">
+           ${h.checkbox('recursive',value="True", label=_('apply to parents'))}
+           <span class="help-block">${_('Set or revoke permission to all children of that group, including repositories and other groups')}</span>
+        </td>
+    </tr>
 </table>
 <script type="text/javascript">
 function ajaxActionUser(user_id, field_id) {
@@ -81,7 +87,8 @@
             alert("${_('Failed to remove user')}");
         },
     };
-    var postData = '_method=delete&user_id=' + user_id;
+    var recursive = YUD.get('recursive').checked;
+    var postData = '_method=delete&recursive={0}&user_id={1}'.format(recursive,user_id);
     var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
 };
 
@@ -96,7 +103,8 @@
             alert("${_('Failed to remove users group')}");
         },
     };
-    var postData = '_method=delete&users_group_id='+users_group_id;
+    var recursive = YUD.get('recursive').checked;
+    var postData = '_method=delete&recursive={0}&users_group_id={1}'.format(recursive,users_group_id);
     var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
 };
 
--- a/rhodecode/templates/admin/repos_groups/repos_groups.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/admin/repos_groups/repos_groups.html	Fri Sep 07 23:20:33 2012 +0200
@@ -5,7 +5,8 @@
 </%def>
 
 <%def name="breadcrumbs()">
-    <span class="groups_breadcrumbs"> ${_('Groups')}
+    <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
--- a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html	Fri Sep 07 23:20:33 2012 +0200
@@ -69,7 +69,7 @@
                     ${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>
             <div class="buttons">
               ${h.submit('save',_('Save'),class_="ui-btn large")}
               ${h.reset('reset',_('Reset'),class_="ui-btn large")}
--- a/rhodecode/templates/admin/settings/settings.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/admin/settings/settings.html	Fri Sep 07 23:20:33 2012 +0200
@@ -154,7 +154,7 @@
                             <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>[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>
@@ -186,7 +186,7 @@
                 </div>
                 <div class="checkboxes">
 					<div class="checkbox">
-						${h.checkbox('web_push_ssl','true')}
+						${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>
@@ -237,9 +237,9 @@
                     ##    ${h.checkbox('extensions_hggit','True')}
                     ##    <label for="extensions_hggit">${_('hg-git extensions')}</label>
                     ##</div>
-                    ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>                    
+                    ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>
                 </div>
-             </div>             
+             </div>
             <div class="field">
                 <div class="label">
                     <label for="paths_root_path">${_('Repositories location')}:</label>
--- a/rhodecode/templates/admin/users/user_edit.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/admin/users/user_edit.html	Fri Sep 07 23:20:33 2012 +0200
@@ -152,7 +152,7 @@
                 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
                                              'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
              </div>
-             <div id="inherit_overlay" style="${'opacity:0.3' if c.user.inherit_default_permissions else ''}" >        
+             <div id="inherit_overlay" style="${'opacity:0.3' if c.user.inherit_default_permissions else ''}" >
              <div class="field">
                 <div class="label label-checkbox">
                     <label for="create_repo_perm">${_('Create repositories')}:</label>
@@ -169,7 +169,7 @@
                     ${h.checkbox('fork_repo_perm',value=True)}
                 </div>
              </div>
-             </div>             
+             </div>
             <div class="buttons">
               ${h.submit('save',_('Save'),class_="ui-btn large")}
               ${h.reset('reset',_('Reset'),class_="ui-btn large")}
--- a/rhodecode/templates/admin/users_groups/users_group_edit.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/admin/users_groups/users_group_edit.html	Fri Sep 07 23:20:33 2012 +0200
@@ -112,7 +112,7 @@
                 </div>
                 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
                                              'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
-             </div>        
+             </div>
              <div id="inherit_overlay" style="${'opacity:0.3' if c.users_group.inherit_default_permissions else ''}" >
              <div class="field">
                 <div class="label label-checkbox">
@@ -130,7 +130,7 @@
                     ${h.checkbox('fork_repo_perm',value=True)}
                 </div>
              </div>
-             </div>          
+             </div>
             <div class="buttons">
               ${h.submit('save',_('Save'),class_="ui-btn large")}
               ${h.reset('reset',_('Reset'),class_="ui-btn large")}
--- a/rhodecode/templates/base/root.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/base/root.html	Fri Sep 07 23:20:33 2012 +0200
@@ -90,7 +90,7 @@
                     f.setAttribute('class','follow');
                     f.setAttribute('title',_TM['Start following this repository']);
                     if(f_cnt){
-                        var cnt = Number(f_cnt.innerHTML)+1;
+                        var cnt = Number(f_cnt.innerHTML)-1;
                         f_cnt.innerHTML = cnt;
                     }
                 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/email_templates/pull_request.html	Fri Sep 07 23:20:33 2012 +0200
@@ -0,0 +1,20 @@
+## -*- coding: utf-8 -*-
+<%inherit file="main.html"/>
+
+User <b>${pr_user_created}</b> opened pull request for repository
+${pr_repo_url} and wants you to review changes.
+
+<div>title: ${pr_title}</div>
+<div>description:</div>
+<p>
+${body}
+</p>
+
+<div>revisions for reviewing</div>
+<ul>
+%for r in pr_revisions:
+    <li>${r}</li>
+%endfor
+</ul>
+
+View this pull request here: ${pr_url}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/email_templates/pull_request_comment.html	Fri Sep 07 23:20:33 2012 +0200
@@ -0,0 +1,15 @@
+## -*- coding: utf-8 -*-
+<%inherit file="main.html"/>
+
+User <b>${pr_comment_user}</b> commented on pull request #${pr_id} for
+repository ${pr_target_repo}
+
+<p>
+${body}
+
+%if status_change:
+    <span>New status -> ${status_change}</span>
+%endif
+</p>
+
+View this comment here: ${pr_comment_url}
--- a/rhodecode/templates/files/files.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/files/files.html	Fri Sep 07 23:20:33 2012 +0200
@@ -47,38 +47,38 @@
 
 var ypjax_links = function(){
     YUE.on(YUQ('.ypjax-link'), 'click',function(e){
-    	
+
     	//don't do ypjax on middle click
-    	if(e.which == 2 || !History.enabled){ 
+    	if(e.which == 2 || !History.enabled){
     		return true;
     	}
-    	
+
         var el = e.currentTarget;
         var url = el.href;
 
         var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
         _base_url = _base_url.replace('//','/')
-        
+
         //extract rev and the f_path from url.
         parts = url.split(_base_url)
         if(parts.length != 2){
         	return false;
         }
-        
+
         var parts2 = parts[1].split('/');
       	var rev = parts2.shift(); // pop the first element which is the revision
       	var f_path = parts2.join('/');
-        
+
         var title = "${_('%s files') % c.repo_name}" + " - " + f_path;
-        
+
         var _node_list_url = node_list_url.replace('__REV__',rev);
         var _url_base = url_base.replace('__REV__',rev).replace('__FPATH__', f_path);
 
         // Change our States and save some data for handling events
         var data = {url:url,title:title, url_base:_url_base,
                     node_list_url:_node_list_url};
-        History.pushState(data, title, url);        
-        
+        History.pushState(data, title, url);
+
         //now we're sure that we can do ypjax things
         YUE.preventDefault(e)
         return false;
@@ -92,10 +92,10 @@
     // Inform Google Analytics of the change
     if ( typeof window.pageTracker !== 'undefined' ) {
         window.pageTracker._trackPageview(State.url);
-    }	
+    }
 }
 
-YUE.onDOMReady(function(){ 
+YUE.onDOMReady(function(){
     ypjax_links();
     var container = 'files_data';
     //Bind to StateChange Event
@@ -124,8 +124,8 @@
             }
           });
         }
-    });    
- 
+    });
+
     // init the search filter
     var _State = {
        url: "${h.url.current()}",
--- a/rhodecode/templates/files/files_ypjax.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/files/files_ypjax.html	Fri Sep 07 23:20:33 2012 +0200
@@ -9,7 +9,7 @@
             <%include file='files_browser.html'/>
         %else:
             <%include file='files_source.html'/>
-        %endif        
+        %endif
 %else:
     <h2>
         <a href="#" onClick="javascript:parent.history.back();" target="main">${_('Go back')}</a>
--- a/rhodecode/templates/pullrequests/pullrequest_show.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/pullrequests/pullrequest_show.html	Fri Sep 07 23:20:33 2012 +0200
@@ -20,10 +20,10 @@
         ${self.breadcrumbs()}
     </div>
         %if c.pull_request.is_closed():
-        <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))}</div>
+        <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))} ${_('with status %s') % h.changeset_status_lbl(c.current_changeset_status)}</div>
         %endif
     <h3>${_('Title')}: ${c.pull_request.title}</h3>
-        
+
     <div class="form">
       <div id="summary" class="fields">
          <div class="field">
@@ -46,9 +46,9 @@
           <div class="input">
             <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
           </div>
-         </div>                
+         </div>
       </div>
-    </div>    
+    </div>
     <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
     <div style="padding:4px 4px 10px 20px">
       <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
--- a/rhodecode/templates/summary/summary.html	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/templates/summary/summary.html	Fri Sep 07 23:20:33 2012 +0200
@@ -7,7 +7,7 @@
 <%def name="breadcrumbs_links()">
     ${h.link_to(_(u'Home'),h.url('/'))}
     &raquo;
-    ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
+    ${h.repo_link(c.dbrepo.groups_and_repo)}
     &raquo;
     ${_('summary')}
 </%def>
@@ -108,7 +108,7 @@
                 <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        
+              %endif
 			 </div>
 
 			 <div class="field">
--- a/rhodecode/tests/functional/test_compare.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/tests/functional/test_compare.py	Fri Sep 07 23:20:33 2012 +0200
@@ -30,17 +30,17 @@
         response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
 
         ## files diff
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2))
 
     def test_index_branch(self):
         self.log_user()
@@ -183,7 +183,111 @@
             ## files
             response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s#C--826e8142e6ba">file1</a>""" % (r2_name, rev1, rev2))
 
-
         finally:
             RepoModel().delete(r1_id)
             RepoModel().delete(r2_id)
+
+    def test_org_repo_new_commits_after_forking(self):
+        self.log_user()
+
+        repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
+                                        description='diff-test',
+                                        owner=TEST_USER_ADMIN_LOGIN)
+
+        Session().commit()
+        r1_id = repo1.repo_id
+        r1_name = repo1.repo_name
+
+        #commit something initially !
+        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='commit1',
+            content='line1',
+            f_path='file1'
+        )
+        Session().commit()
+        self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
+        #fork the repo1
+        repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
+                                description='compare-test',
+                                clone_uri=repo1.repo_full_path,
+                                owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
+        Session().commit()
+        self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
+        r2_id = repo2.repo_id
+        r2_name = repo2.repo_name
+
+        #make 3 new commits in fork
+        cs1 = ScmModel().create_node(
+            repo=repo2.scm_instance, repo_name=r2_name,
+            cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
+            author=TEST_USER_ADMIN_LOGIN,
+            message='commit1-fork',
+            content='file1-line1-from-fork',
+            f_path='file1-fork'
+        )
+        cs2 = ScmModel().create_node(
+            repo=repo2.scm_instance, repo_name=r2_name,
+            cs=cs1, user=TEST_USER_ADMIN_LOGIN,
+            author=TEST_USER_ADMIN_LOGIN,
+            message='commit2-fork',
+            content='file2-line1-from-fork',
+            f_path='file2-fork'
+        )
+        cs3 = ScmModel().create_node(
+            repo=repo2.scm_instance, repo_name=r2_name,
+            cs=cs2, user=TEST_USER_ADMIN_LOGIN,
+            author=TEST_USER_ADMIN_LOGIN,
+            message='commit3-fork',
+            content='file3-line1-from-fork',
+            f_path='file3-fork'
+        )
+
+        #compare !
+        rev1 = 'default'
+        rev2 = 'default'
+        response = self.app.get(url(controller='compare', action='index',
+                                    repo_name=r2_name,
+                                    org_ref_type="branch",
+                                    org_ref=rev1,
+                                    other_ref_type="branch",
+                                    other_ref=rev2,
+                                    repo=r1_name
+                                    ))
+
+        try:
+            response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
+            response.mustcontain("""file1-line1-from-fork""")
+            response.mustcontain("""file2-line1-from-fork""")
+            response.mustcontain("""file3-line1-from-fork""")
+
+            #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',
+                content='line1',
+                f_path='file2'
+            )
+            #compare !
+            rev1 = 'default'
+            rev2 = 'default'
+            response = self.app.get(url(controller='compare', action='index',
+                                        repo_name=r2_name,
+                                        org_ref_type="branch",
+                                        org_ref=rev1,
+                                        other_ref_type="branch",
+                                        other_ref=rev2,
+                                        repo=r1_name
+                                        ))
+
+            response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
+            response.mustcontain("""file1-line1-from-fork""")
+            response.mustcontain("""file2-line1-from-fork""")
+            response.mustcontain("""file3-line1-from-fork""")
+        finally:
+            RepoModel().delete(r2_id)
+            RepoModel().delete(r1_id)
--- a/rhodecode/tests/functional/test_summary.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/tests/functional/test_summary.py	Fri Sep 07 23:20:33 2012 +0200
@@ -1,6 +1,9 @@
 from rhodecode.tests import *
 from rhodecode.model.db import Repository
 from rhodecode.lib.utils import invalidate_cache
+from rhodecode.model.repo import RepoModel
+from rhodecode.tests.models.common import _make_repo
+from rhodecode.model.meta import Session
 
 
 class TestSummaryController(TestController):
@@ -82,6 +85,20 @@
                         """title="public repository" alt="public """
                         """repository" src="/images/icons/lock_open.png"/>""")
 
+    def test_index_by_repo_having_id_path_in_name_hg(self):
+        self.log_user()
+        _make_repo(name='repo_1')
+        Session().commit()
+        response = self.app.get(url(controller='summary',
+                                    action='index',
+                                    repo_name='repo_1'))
+
+        try:
+            response.mustcontain("""repo_1""")
+        finally:
+            RepoModel().delete(Repository.get_by_repo_name('repo_1'))
+            Session().commit()
+
     def test_index_by_id_git(self):
         self.log_user()
         ID = Repository.get_by_repo_name(GIT_REPO).repo_id
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/models/common.py	Fri Sep 07 23:20:33 2012 +0200
@@ -0,0 +1,116 @@
+import os
+import unittest
+import functools
+from rhodecode.tests import *
+
+
+from rhodecode.model.repos_group import ReposGroupModel
+from rhodecode.model.repo import RepoModel
+from rhodecode.model.db import RepoGroup, Repository, User
+from rhodecode.model.user import UserModel
+
+from rhodecode.lib.auth import AuthUser
+from rhodecode.model.meta import Session
+
+
+def _make_group(path, desc='desc', parent_id=None,
+                 skip_if_exists=False):
+
+    gr = RepoGroup.get_by_group_name(path)
+    if gr and skip_if_exists:
+        return gr
+    if isinstance(parent_id, RepoGroup):
+        parent_id = parent_id.group_id
+    gr = ReposGroupModel().create(path, desc, parent_id)
+    return gr
+
+
+def _make_repo(name, repos_group=None, repo_type='hg'):
+    return RepoModel().create_repo(name, repo_type, 'desc',
+                                   TEST_USER_ADMIN_LOGIN,
+                                   repos_group=repos_group)
+
+
+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()):
+        if isinstance(el, Repository):
+            RepoModel().delete(el)
+        elif isinstance(el, RepoGroup):
+            ReposGroupModel().delete(el, force_delete=True)
+
+    u = User.get(test_u1_id)
+    Session().delete(u)
+    Session().commit()
+
+
+def _create_project_tree():
+    """
+    Creates a tree of groups and repositories to test permissions
+
+    structure
+     [g0] - group `g0` with 3 subgroups
+     |
+     |__[g0_1] group g0_1 with 2 groups 0 repos
+     |  |
+     |  |__[g0_1_1] group g0_1_1 with 1 group 2 repos
+     |  |   |__<g0/g0_1/g0_1_1/g0_1_1_r1>
+     |  |   |__<g0/g0_1/g0_1_1/g0_1_1_r2>
+     |  |__<g0/g0_1/g0_1_r1>
+     |
+     |__[g0_2] 2 repos
+     |  |
+     |  |__<g0/g0_2/g0_2_r1>
+     |  |__<g0/g0_2/g0_2_r2>
+     |
+     |__[g0_3] 1 repo
+        |
+        |_<g0/g0_3/g0_3_r1>
+
+    """
+    test_u1 = UserModel().create_or_update(
+        username=u'test_u1', password=u'qweqwe',
+        email=u'test_u1@rhodecode.org', firstname=u'test_u1', lastname=u'test_u1'
+    )
+    g0 = _make_group('g0')
+    g0_1 = _make_group('g0_1', parent_id=g0)
+    g0_1_1 = _make_group('g0_1_1', parent_id=g0_1)
+    g0_1_1_r1 = _make_repo('g0/g0_1/g0_1_1/g0_1_1_r1', repos_group=g0_1_1)
+    g0_1_1_r2 = _make_repo('g0/g0_1/g0_1_1/g0_1_1_r2', repos_group=g0_1_1)
+    g0_1_r1 = _make_repo('g0/g0_1/g0_1_r1', repos_group=g0_1)
+    g0_2 = _make_group('g0_2', parent_id=g0)
+    g0_2_r1 = _make_repo('g0/g0_2/g0_2_r1', repos_group=g0_2)
+    g0_2_r2 = _make_repo('g0/g0_2/g0_2_r2', repos_group=g0_2)
+    g0_3 = _make_group('g0_3', parent_id=g0)
+    g0_3_r1 = _make_repo('g0/g0_3/g0_3_r1', repos_group=g0_3)
+    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()
+    if objects:
+        return objs
+    return len(objs)
+
+
+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))
+
+
+def check_tree_perms(obj_name, repo_perm, prefix, expected_perm):
+    assert repo_perm == expected_perm, ('obj:`%s` got perm:`%s` should:`%s`'
+                                    % (obj_name, repo_perm, expected_perm))
+
+
+def _get_perms(filter_='', recursive=True, 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_):
+            yield k, v
+        elif not recursive:
+            if k == filter_:
+                yield k, v
--- a/rhodecode/tests/models/test_permissions.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/tests/models/test_permissions.py	Fri Sep 07 23:20:33 2012 +0200
@@ -1,7 +1,7 @@
 import os
 import unittest
 from rhodecode.tests import *
-
+from rhodecode.tests.models.common import _make_group
 from rhodecode.model.repos_group import ReposGroupModel
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.db import RepoGroup, User, UsersGroupRepoGroupToPerm
@@ -12,16 +12,6 @@
 from rhodecode.lib.auth import AuthUser
 
 
-def _make_group(path, desc='desc', parent_id=None,
-                 skip_if_exists=False):
-
-    gr = RepoGroup.get_by_group_name(path)
-    if gr and skip_if_exists:
-        return gr
-
-    gr = ReposGroupModel().create(path, desc, parent_id)
-    return gr
-
 
 class TestPermissions(unittest.TestCase):
     def __init__(self, methodName='runTest'):
@@ -435,4 +425,3 @@
                          set(['hg.create.repository', 'hg.fork.repository',
                               'hg.register.manual_activate',
                               'repository.read']))
-
--- a/rhodecode/tests/models/test_repos_groups.py	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/tests/models/test_repos_groups.py	Fri Sep 07 23:20:33 2012 +0200
@@ -4,7 +4,7 @@
 
 from rhodecode.model.repos_group import ReposGroupModel
 from rhodecode.model.repo import RepoModel
-from rhodecode.model.db import RepoGroup, User
+from rhodecode.model.db import RepoGroup, User, Repository
 from rhodecode.model.meta import Session
 from sqlalchemy.exc import IntegrityError
 
@@ -15,7 +15,8 @@
     gr = RepoGroup.get_by_group_name(path)
     if gr and skip_if_exists:
         return gr
-
+    if isinstance(parent_id, RepoGroup):
+        parent_id = parent_id.group_id
     gr = ReposGroupModel().create(path, desc, parent_id)
     return gr
 
@@ -54,7 +55,8 @@
             group_parent_id=parent_id,
             perms_updates=[],
             perms_new=[],
-            enable_locking=False
+            enable_locking=False,
+            recursive=False
         )
         gr = ReposGroupModel().update(id_, form_data)
         return gr
@@ -132,7 +134,8 @@
                          repo_type='hg',
                          clone_uri=None,
                          landing_rev='tip',
-                         enable_locking=False)
+                         enable_locking=False,
+                         recursive=False)
         cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
         r = RepoModel().create(form_data, cur_user)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/models/test_user_permissions_on_groups.py	Fri Sep 07 23:20:33 2012 +0200
@@ -0,0 +1,161 @@
+import os
+import unittest
+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
+from rhodecode.model.repo import RepoModel
+
+
+test_u1_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_u1_id, perm, 'user']]
+    ReposGroupModel()._update_permissions(repos_group,
+                                          perms_updates=perms_updates,
+                                          recursive=recursive)
+    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_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_users_group_permissions_on_groups.py	Fri Sep 07 23:20:33 2012 +0200
@@ -0,0 +1,170 @@
+import os
+import unittest
+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
+from rhodecode.model.users_group import UsersGroupModel
+from rhodecode.model.repo import RepoModel
+
+
+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)
+    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 = UsersGroupModel().create(name='perms_group_1')
+    Session().commit()
+    test_u2_gr_id = gr1.users_group_id
+    UsersGroupModel().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/scripts/create_rc.sh	Mon Sep 03 22:22:58 2012 +0200
+++ b/rhodecode/tests/scripts/create_rc.sh	Fri Sep 07 23:20:33 2012 +0200
@@ -3,10 +3,14 @@
 paster setup-rhodecode rc.ini -q --user=marcink --password=qweqwe --email=marcin@python-blog.com --repos=/home/marcink/repos
 API_KEY=`psql -R " " -A -U postgres -h localhost -c "select api_key from users where admin=TRUE" -d rhodecode | awk '{print $2}'`
 echo "run those after running server"
-echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo1 password:qweqwe email:demo1@rhodecode.org"
-echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo2 password:qweqwe email:demo2@rhodecode.org"
-echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo3 password:qweqwe email:demo3@rhodecode.org"
-echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_users_group group_name:demo12"
-echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_users_group usersgroupid:demo12 userid:demo1"
-echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_users_group usersgroupid:demo12 userid:demo2"
-
+paster serve rc.ini --pid-file=rc.pid --daemon
+sleep 3
+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
+echo "killing server"
+kill `cat rc.pid`
+rm rc.pid