changeset 2165:dc2584ba5fbc

merged beta into default branch
author Marcin Kuzminski <marcin@python-works.com>
date Wed, 28 Mar 2012 19:54:16 +0200
parents 8fd6650bb436 (current diff) 0d2ce995f6a4 (diff)
children a794f27381a8
files .hgignore README.rst development.ini docs/api/api.rst docs/changelog.rst docs/index.rst docs/setup.rst docs/upgrade.rst production.ini requires.txt rhodecode/__init__.py rhodecode/config/deployment.ini_tmpl rhodecode/config/environment.py rhodecode/controllers/admin/repos.py rhodecode/controllers/admin/users.py rhodecode/controllers/api/__init__.py rhodecode/controllers/api/api.py rhodecode/controllers/branches.py rhodecode/controllers/changeset.py rhodecode/controllers/feed.py rhodecode/controllers/files.py rhodecode/controllers/home.py rhodecode/controllers/summary.py rhodecode/lib/__init__.py rhodecode/lib/auth.py rhodecode/lib/base.py rhodecode/lib/celerylib/__init__.py rhodecode/lib/celerylib/tasks.py rhodecode/lib/db_manage.py rhodecode/lib/helpers.py rhodecode/lib/hooks.py rhodecode/lib/indexers/__init__.py rhodecode/lib/indexers/daemon.py rhodecode/lib/middleware/https_fixup.py rhodecode/lib/middleware/simplegit.py rhodecode/lib/middleware/simplehg.py rhodecode/lib/utils.py rhodecode/model/__init__.py rhodecode/model/db.py rhodecode/model/repo.py rhodecode/model/scm.py rhodecode/model/user.py rhodecode/public/css/style.css rhodecode/templates/admin/repos/repos.html rhodecode/templates/admin/users/user_edit_my_account.html rhodecode/templates/changelog/changelog.html rhodecode/templates/changeset/changeset.html rhodecode/templates/index.html rhodecode/templates/journal/journal.html setup.py test.ini
diffstat 86 files changed, 2464 insertions(+), 1389 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sat Mar 03 03:41:19 2012 +0200
+++ b/.hgignore	Wed Mar 28 19:54:16 2012 +0200
@@ -6,6 +6,7 @@
 *.egg
 
 syntax: regexp
+^rcextensions
 ^build
 ^docs/build/
 ^docs/_build/
--- a/README.rst	Sat Mar 03 03:41:19 2012 +0200
+++ b/README.rst	Wed Mar 28 19:54:16 2012 +0200
@@ -15,10 +15,11 @@
 however RhodeCode can be run as standalone hosted application on your own server.
 It is open source and donation ware and focuses more on providing a customized, 
 self administered interface for Mercurial_ and GIT_  repositories. 
-RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to 
-handle multiple different version control systems.
+RhodeCode works on *nix systems and Windows it is powered by a vcs_ library 
+that Lukasz Balcerzak and Marcin Kuzminski created to handle multiple 
+different version control systems.
 
-RhodeCode uses `Semantic Versioning <http://semver.org/>`_
+RhodeCode uses `PEP386 versioning http://www.python.org/dev/peps/pep-0386/`_
 
 Installation
 ------------
@@ -99,7 +100,7 @@
 - Intelligent cache with invalidation after push or project change, provides 
   high performance and always up to date data.
 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
-- Async tasks for speed and performance using celery_ (works without them too)  
+- Optional async tasks for speed and performance using celery_  
 - Backup scripts can do backup of whole app and send it over scp to desired 
   location 
 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
--- a/development.ini	Sat Mar 03 03:41:19 2012 +0200
+++ b/development.ini	Wed Mar 28 19:54:16 2012 +0200
@@ -93,6 +93,11 @@
 ## all running rhodecode instances. Leave empty if you don't use it
 instance_id = 
 
+## alternative return HTTP header for failed authentication. Default HTTP
+## response is 401 HTTPUnauthorized. Currently HG clients have troubles with 
+## handling that. Set this variable to 403 to return HTTPForbidden
+auth_ret_code =
+
 ####################################
 ###        CELERY CONFIG        ####
 ####################################
@@ -171,6 +176,7 @@
 
 beaker.session.type = file
 beaker.session.key = rhodecode
+# secure cookie requires AES python libraries
 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
 #beaker.session.validate_key = 9712sds2212c--zxc123
 beaker.session.timeout = 36000
@@ -207,13 +213,13 @@
 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
 sqlalchemy.db1.echo = false
 sqlalchemy.db1.pool_recycle = 3600
-sqlalchemy.convert_unicode = true
+sqlalchemy.db1.convert_unicode = true
 
 ################################
 ### LOGGING CONFIGURATION   ####
 ################################
 [loggers]
-keys = root, routes, rhodecode, sqlalchemy, beaker, templates
+keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
 
 [handlers]
 keys = console, console_sql
@@ -259,6 +265,12 @@
 qualname = sqlalchemy.engine
 propagate = 0
 
+[logger_whoosh_indexer]
+level = DEBUG
+handlers = 
+qualname = whoosh_indexer
+propagate = 1
+
 ##############
 ## HANDLERS ##
 ##############
--- a/docs/api/api.rst	Sat Mar 03 03:41:19 2012 +0200
+++ b/docs/api/api.rst	Wed Mar 28 19:54:16 2012 +0200
@@ -27,7 +27,7 @@
 All clients are required to send JSON-RPC spec JSON data::
 
     {   
-        "id:<id>,
+        "id:"<id>",
         "api_key":"<api_key>",
         "method":"<method_name>",
         "args":{"<arg_key>":"<arg_val>"}
@@ -50,9 +50,9 @@
 RhodeCode API will return always a JSON-RPC response::
 
     {   
-        "id":<id>,
-        "result": "<result>",
-        "error": null
+        "id":<id>, # matching id sent by request
+        "result": "<result>"|null, # JSON formatted result, null if any errors
+        "error": "null"|<error_message> # JSON formatted error (if any)
     }
 
 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
@@ -72,6 +72,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "pull"
     args :    {
@@ -94,6 +95,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "get_user"
     args :    { 
@@ -111,7 +113,15 @@
                 "email" :    "<email>",
                 "active" :   "<bool>",
                 "admin" :    "<bool>",
-                "ldap_dn" :  "<ldap_dn>"
+                "ldap_dn" :  "<ldap_dn>",
+                "last_login": "<last_login>",
+                "permissions": {
+                    "global": ["hg.create.repository",
+                               "repository.read",
+                               "hg.register.manual_activate"],
+                    "repositories": {"repo1": "repository.none"},
+                    "repositories_groups": {"Group1": "group.read"}
+                 },
             }
 
     error:  null
@@ -126,6 +136,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "get_users"
     args :    { }
@@ -141,7 +152,8 @@
                 "email" :    "<email>",
                 "active" :   "<bool>",
                 "admin" :    "<bool>",
-                "ldap_dn" :  "<ldap_dn>"
+                "ldap_dn" :  "<ldap_dn>",
+                "last_login": "<last_login>",
               },

             ]
@@ -157,6 +169,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "create_user"
     args :    {
@@ -188,6 +201,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "update_user"
     args :    {
@@ -220,6 +234,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "get_users_group"
     args :    {
@@ -258,6 +273,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "get_users_groups"
     args :    { }
@@ -296,6 +312,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "create_users_group"
     args:     {
@@ -322,6 +339,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "add_user_users_group"
     args:     {
@@ -350,6 +368,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "remove_user_from_users_group"
     args:     {
@@ -370,12 +389,14 @@
 get_repo
 --------
 
-Gets an existing repository by it's name or repository_id. This command can 
+Gets an existing repository by it's name or repository_id. Members will return
+either users_group or user associated to that repository. This command can 
 be executed only using api_key belonging to user with admin rights.
 
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "get_repo"
     args:     {
@@ -391,7 +412,9 @@
                 "type" :        "<type>",
                 "description" : "<description>",
                 "members" :     [
-                                  { "id" :         "<userid>",
+                                  { 
+                                    "type": "user",
+                                    "id" :         "<userid>",
                                     "username" :   "<username>",
                                     "firstname":   "<firstname>",
                                     "lastname" :   "<lastname>",
@@ -402,7 +425,8 @@
                                     "permission" : "repository.(read|write|admin)"
                                   },

-                                  {
+                                  { 
+                                    "type": "users_group",
                                     "id" :       "<usersgroupid>",
                                     "name" :     "<usersgroupname>",
                                     "active":    "<bool>",
@@ -423,6 +447,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "get_repos"
     args:     { }
@@ -452,6 +477,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "get_repo_nodes"
     args:     {
@@ -485,6 +511,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "create_repo"
     args:     {
@@ -514,6 +541,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "delete_repo"
     args:     {
@@ -538,6 +566,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "grant_user_permission"
     args:     {
@@ -563,6 +592,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method  : "revoke_user_permission"
     args:     {
@@ -588,6 +618,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method :  "grant_users_group_permission"
     args:     {
@@ -612,6 +643,7 @@
 
 INPUT::
 
+    id : <id_for_response>
     api_key : "<api_key>"
     method  : "revoke_users_group_permission"
     args:     {
--- a/docs/changelog.rst	Sat Mar 03 03:41:19 2012 +0200
+++ b/docs/changelog.rst	Wed Mar 28 19:54:16 2012 +0200
@@ -5,7 +5,45 @@
 =========
 
 
+1.3.4 (**2012-03-28**)
+----------------------
 
+news
+++++
+
+- Whoosh logging is now controlled by the .ini files logging setup
+- added clone-url into edit form on /settings page
+- added help text into repo add/edit forms
+- created rcextensions module with additional mappings (ref #322) and
+  post push/pull/create repo hooks callbacks
+- implemented #377 Users view for his own permissions on account page
+- #399 added inheritance of permissions for users group on repos groups
+- #401 repository group is automatically pre-selected when adding repos 
+  inside a repository group
+- added alternative HTTP 403 response when client failed to authenticate. Helps 
+  solving issues with Mercurial and LDAP
+- #402 removed group prefix from repository name when listing repositories 
+  inside a group
+- added gravatars into permission view and permissions autocomplete
+- #347 when running multiple RhodeCode instances, properly invalidates cache 
+  for all registered servers
+
+fixes
++++++
+
+- fixed #390 cache invalidation problems on repos inside group
+- fixed #385 clone by ID url was loosing proxy prefix in URL
+- fixed some unicode problems with waitress
+- fixed issue with escaping < and > in changeset commits
+- fixed error occurring during recursive group creation in API 
+  create_repo function
+- fixed #393 py2.5 fixes for routes url generator
+- fixed #397 Private repository groups shows up before login
+- fixed #396 fixed problems with revoking users in nested groups
+- fixed mysql unicode issues + specified InnoDB as default engine with 
+  utf8 charset
+- #406 trim long branch/tag names in changelog to not break UI
+  
 1.3.3 (**2012-03-02**)
 ----------------------
 
--- a/docs/index.rst	Sat Mar 03 03:41:19 2012 +0200
+++ b/docs/index.rst	Wed Mar 28 19:54:16 2012 +0200
@@ -23,7 +23,8 @@
    usage/git_support
    usage/statistics
    usage/backup
-   
+   usage/debugging
+
 **Develop**
 
 .. toctree::
--- a/docs/setup.rst	Sat Mar 03 03:41:19 2012 +0200
+++ b/docs/setup.rst	Wed Mar 28 19:54:16 2012 +0200
@@ -20,9 +20,10 @@
 
 
 Next, you need to create the databases used by RhodeCode. I recommend that you
-use sqlite (default) or postgresql. If you choose a database other than the
+use postgresql or sqlite (default). If you choose a database other than the
 default ensure you properly adjust the db url in your production.ini
-configuration file to use this other database. Create the databases by running
+configuration file to use this other database. RhodeCode currently supports
+postgresql, sqlite and mysql databases. Create the database by running
 the following command::
 
     paster setup-app production.ini
@@ -57,15 +58,18 @@
 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
   well as edit more advanced options on users and repositories
 
-Try copying your own mercurial repository into the "root" directory you are
-using, then from within the RhodeCode web application choose Admin >
-repositories. Then choose Add New Repository. Add the repository you copied 
-into the root. Test that you can browse your repository from within RhodeCode 
-and then try cloning your repository from RhodeCode with::
+Optionally users can create `rcextensions` package that extends RhodeCode
+functionality. To do this simply execute::
+
+    paster make-rcext production.ini
 
-    hg clone http://127.0.0.1:5000/<repository name>
+This will create `rcextensions` package in the same place that your `ini` file
+lives. With `rcextensions` it's possible to add additional mapping for whoosh, 
+stats and add additional code into the push/pull/create repo hooks. For example
+for sending signals to build-bots such as jenkins.
+Please see the `__init__.py` file inside `rcextensions` package 
+for more details.
 
-where *repository name* is replaced by the name of your repository.
 
 Using RhodeCode with SSH
 ------------------------
--- a/docs/upgrade.rst	Sat Mar 03 03:41:19 2012 +0200
+++ b/docs/upgrade.rst	Wed Mar 28 19:54:16 2012 +0200
@@ -47,6 +47,9 @@
 and will always recheck the settings of the application, if there are no new 
 options that need to be set.
 
+.. note::
+   If you're using Celery, make sure you restart all instances of it after
+   upgrade.
 
 .. _virtualenv: http://pypi.python.org/pypi/virtualenv  
 .. _python: http://www.python.org/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/usage/debugging.rst	Wed Mar 28 19:54:16 2012 +0200
@@ -0,0 +1,30 @@
+.. _debugging:
+
+===================
+Debugging RhodeCode
+===================
+
+If you encountered problems with RhodeCode here are some instructions how to
+possibly debug them.
+
+** First make sure you're using the latest version available.**
+
+enable detailed debug
+---------------------
+
+RhodeCode uses standard python logging modules to log it's output.
+By default only loggers with INFO level are displayed. To enable full output
+change `level = DEBUG` for all logging handlers in currently used .ini file. 
+This change will allow to see much more detailed output in the logfile or
+console. This generally helps a lot to track issues.
+
+
+enable interactive debug mode
+-----------------------------
+
+To enable interactive debug mode simply comment out `set debug = false` in
+.ini file, this will trigger and interactive debugger each time there an
+error in browser, or send a http link if error occured in the backend. This
+is a great tool for fast debugging as you get a handy python console right
+in the web view. ** NEVER ENABLE THIS ON PRODUCTION ** the interactive console
+can be a serious security threat to you system.
--- a/docs/usage/general.rst	Sat Mar 03 03:41:19 2012 +0200
+++ b/docs/usage/general.rst	Wed Mar 28 19:54:16 2012 +0200
@@ -71,6 +71,11 @@
 on errors the mails will have a detailed traceback of error.
 
 
+Mails are also sent for code comments. If someone comments on a changeset
+mail is sent to all participants, the person who commited the changeset 
+(if present in RhodeCode), and to all people mentioned with @mention system.
+
+
 Trending source files
 ---------------------
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/init.d/rhodecode-daemon4	Wed Mar 28 19:54:16 2012 +0200
@@ -0,0 +1,70 @@
+#!/bin/bash
+###########################################
+#### THIS IS AN ARCH LINUX RC.D SCRIPT ####
+###########################################
+
+. /etc/rc.conf
+. /etc/rc.d/functions
+
+DAEMON=rhodecode
+APP_HOMEDIR="/srv"
+APP_PATH="$APP_HOMEDIR/$DAEMON"
+CONF_NAME="production.ini"
+LOG_FILE="/var/log/$DAEMON.log"
+PID_FILE="/run/daemons/$DAEMON"
+APPL=/usr/bin/paster
+RUN_AS="*****"
+
+ARGS="serve --daemon \
+--user=$RUN_AS \
+--group=$RUN_AS \
+--pid-file=$PID_FILE \
+--log-file=$LOG_FILE \
+$APP_PATH/$CONF_NAME"
+
+[ -r /etc/conf.d/$DAEMON ] && . /etc/conf.d/$DAEMON
+
+if [[ -r $PID_FILE ]]; then
+    read -r PID < "$PID_FILE"
+    if [[ $PID && ! -d /proc/$PID ]]; then
+        unset PID
+        rm_daemon $DAEMON
+    fi
+fi
+
+case "$1" in
+start)
+    stat_busy "Starting $DAEMON"
+    export HOME=$APP_PATH
+    [ -z "$PID" ] && $APPL $ARGS &>/dev/null
+    if [ $? = 0 ]; then
+        add_daemon $DAEMON
+        stat_done
+    else
+        stat_fail
+        exit 1
+    fi
+    ;;
+stop)
+    stat_busy "Stopping $DAEMON"
+    [ -n "$PID" ] && kill $PID &>/dev/null 
+    if [ $? = 0 ]; then
+        rm_daemon $DAEMON
+        stat_done
+    else
+        stat_fail
+        exit 1
+    fi
+    ;;
+restart)
+    $0 stop
+    sleep 1
+    $0 start
+    ;;
+status)
+    stat_busy "Checking $name status";
+    ck_status $name
+    ;;
+*)
+    echo "usage: $0 {start|stop|restart|status}"
+esac
\ No newline at end of file
--- a/production.ini	Sat Mar 03 03:41:19 2012 +0200
+++ b/production.ini	Wed Mar 28 19:54:16 2012 +0200
@@ -93,6 +93,11 @@
 ## all running rhodecode instances. Leave empty if you don't use it
 instance_id = 
 
+## alternative return HTTP header for failed authentication. Default HTTP
+## response is 401 HTTPUnauthorized. Currently HG clients have troubles with 
+## handling that. Set this variable to 403 to return HTTPForbidden
+auth_ret_code =
+
 ####################################
 ###        CELERY CONFIG        ####
 ####################################
@@ -208,13 +213,13 @@
 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
 sqlalchemy.db1.echo = false
 sqlalchemy.db1.pool_recycle = 3600
-sqlalchemy.convert_unicode = true
+sqlalchemy.db1.convert_unicode = true
 
 ################################
 ### LOGGING CONFIGURATION   ####
 ################################
 [loggers]
-keys = root, routes, rhodecode, sqlalchemy, beaker, templates
+keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
 
 [handlers]
 keys = console, console_sql
@@ -260,6 +265,12 @@
 qualname = sqlalchemy.engine
 propagate = 0
 
+[logger_whoosh_indexer]
+level = DEBUG
+handlers = 
+qualname = whoosh_indexer
+propagate = 1
+
 ##############
 ## HANDLERS ##
 ##############
--- a/requires.txt	Sat Mar 03 03:41:19 2012 +0200
+++ b/requires.txt	Wed Mar 28 19:54:16 2012 +0200
@@ -1,17 +1,17 @@
 Pylons==1.0.0
 Beaker==1.6.3
-WebHelpers>=1.2
+WebHelpers==1.3
 formencode==1.2.4
-SQLAlchemy==0.7.4
-Mako==0.5.0
+SQLAlchemy==0.7.6
+Mako==0.6.2
 pygments>=1.4
 whoosh>=2.3.0,<2.4
 celery>=2.2.5,<2.3
 babel
 python-dateutil>=1.5.0,<2.0.0
-dulwich>=0.8.0,<0.9.0
+https://github.com/jelmer/dulwich/tarball/master
 webob==1.0.8
 markdown==2.1.1
 docutils==0.8.1
 py-bcrypt
-mercurial>=2.1,<2.2
\ No newline at end of file
+mercurial>=2.1,<2.2
--- a/rhodecode/__init__.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/__init__.py	Wed Mar 28 19:54:16 2012 +0200
@@ -4,7 +4,7 @@
     ~~~~~~~~~~~~~~~~~~
 
     RhodeCode, a web based repository management based on pylons
-    versioning implementation: http://semver.org/
+    versioning implementation: http://www.python.org/dev/peps/pep-0386/
 
     :created_on: Apr 9, 2010
     :author: marcink
@@ -26,8 +26,18 @@
 import sys
 import platform
 
-VERSION = (1, 3, 3)
-__version__ = '.'.join((str(each) for each in VERSION[:4]))
+VERSION = (1, 3, 4)
+
+try:
+    from rhodecode.lib import get_current_revision
+    _rev = get_current_revision()
+    if _rev:
+        VERSION += ('dev%s' % _rev[0],)
+except ImportError:
+    pass
+
+__version__ = ('.'.join((str(each) for each in VERSION[:3])) +
+               '.'.join(VERSION[3:]))
 __dbversion__ = 5  # defines current db version for migrations
 __platform__ = platform.system()
 __license__ = 'GPLv3'
@@ -39,16 +49,16 @@
 requirements = [
     "Pylons==1.0.0",
     "Beaker==1.6.3",
-    "WebHelpers>=1.2",
+    "WebHelpers==1.3",
     "formencode==1.2.4",
-    "SQLAlchemy==0.7.4",
-    "Mako==0.5.0",
+    "SQLAlchemy==0.7.6",
+    "Mako==0.6.2",
     "pygments>=1.4",
     "whoosh>=2.3.0,<2.4",
     "celery>=2.2.5,<2.3",
     "babel",
     "python-dateutil>=1.5.0,<2.0.0",
-    "dulwich>=0.8.0,<0.9.0",
+    "dulwich>=0.8.4,<0.9.0",
     "webob==1.0.8",
     "markdown==2.1.1",
     "docutils==0.8.1",
@@ -65,17 +75,6 @@
     requirements.append("mercurial>=2.1,<2.2")
 
 
-try:
-    from rhodecode.lib import get_current_revision
-    _rev = get_current_revision(quiet=True)
-except ImportError:
-    # this is needed when doing some setup.py operations
-    _rev = False
-
-if len(VERSION) > 3 and _rev:
-    __version__ += ' [rev:%s]' % _rev[0]
-
-
 def get_version():
     """Returns shorter version (digit parts only) as string."""
 
@@ -90,3 +89,6 @@
 
 # link to config for pylons
 CONFIG = {}
+
+# Linked module for extensions
+EXTENSIONS = {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/config/conf.py	Wed Mar 28 19:54:16 2012 +0200
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+"""
+    package.rhodecode.config.conf
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Various config settings for RhodeCode
+
+    :created_on: Mar 7, 2012
+    :author: marcink
+    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
+    :license: <name>, see LICENSE_FILE for more details.
+"""
+from rhodecode import EXTENSIONS
+
+from rhodecode.lib.utils2 import __get_lem
+
+
+# language map is also used by whoosh indexer, which for those specified
+# extensions will index it's content
+LANGUAGES_EXTENSIONS_MAP = __get_lem()
+
+#==============================================================================
+# WHOOSH INDEX EXTENSIONS
+#==============================================================================
+# EXTENSIONS WE WANT TO INDEX CONTENT OFF USING WHOOSH
+INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
+
+# list of readme files to search in file tree and display in summary
+# attached weights defines the search  order lower is first
+ALL_READMES = [
+    ('readme', 0), ('README', 0), ('Readme', 0),
+    ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
+    ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
+    ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
+    ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
+]
+
+# extension together with weights to search lower is first
+RST_EXTS = [
+    ('', 0), ('.rst', 1), ('.rest', 1),
+    ('.RST', 2), ('.REST', 2),
+    ('.txt', 3), ('.TXT', 3)
+]
+
+MARKDOWN_EXTS = [
+    ('.md', 1), ('.MD', 1),
+    ('.mkdn', 2), ('.MKDN', 2),
+    ('.mdown', 3), ('.MDOWN', 3),
+    ('.markdown', 4), ('.MARKDOWN', 4)
+]
+
+PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
+
+ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
+
+DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+DATE_FORMAT = "%Y-%m-%d"
--- a/rhodecode/config/deployment.ini_tmpl	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/config/deployment.ini_tmpl	Wed Mar 28 19:54:16 2012 +0200
@@ -93,6 +93,11 @@
 ## all running rhodecode instances. Leave empty if you don't use it
 instance_id = 
 
+## alternative return HTTP header for failed authentication. Default HTTP
+## response is 401 HTTPUnauthorized. Currently HG clients have troubles with 
+## handling that. Set this variable to 403 to return HTTPForbidden
+auth_ret_code =
+
 ####################################
 ###        CELERY CONFIG        ####
 ####################################
@@ -218,13 +223,13 @@
 
 sqlalchemy.db1.echo = false
 sqlalchemy.db1.pool_recycle = 3600
-sqlalchemy.convert_unicode = true
+sqlalchemy.db1.convert_unicode = true
 
 ################################
 ### LOGGING CONFIGURATION   ####
 ################################
 [loggers]
-keys = root, routes, rhodecode, sqlalchemy, beaker, templates
+keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
 
 [handlers]
 keys = console, console_sql
@@ -270,6 +275,12 @@
 qualname = sqlalchemy.engine
 propagate = 0
 
+[logger_whoosh_indexer]
+level = DEBUG
+handlers = 
+qualname = whoosh_indexer
+propagate = 1
+
 ##############
 ## HANDLERS ##
 ##############
--- a/rhodecode/config/environment.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/config/environment.py	Wed Mar 28 19:54:16 2012 +0200
@@ -2,21 +2,24 @@
 
 import os
 import logging
+import rhodecode
 
 from mako.lookup import TemplateLookup
 from pylons.configuration import PylonsConfig
 from pylons.error import handle_mako_error
 
-import rhodecode
+# don't remove this import it does magic for celery
+from rhodecode.lib import celerypylons
+
 import rhodecode.lib.app_globals as app_globals
-import rhodecode.lib.helpers
 
 from rhodecode.config.routing import make_map
-# don't remove this import it does magic for celery
-from rhodecode.lib import celerypylons, str2bool
-from rhodecode.lib import engine_from_config
+
+from rhodecode.lib import helpers
 from rhodecode.lib.auth import set_available_permissions
-from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
+from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config,\
+    load_rcextensions
+from rhodecode.lib.utils2 import engine_from_config, str2bool
 from rhodecode.model import init_model
 from rhodecode.model.scm import ScmModel
 
@@ -24,17 +27,20 @@
 
 
 def load_environment(global_conf, app_conf, initial=False):
-    """Configure the Pylons environment via the ``pylons.config``
+    """
+    Configure the Pylons environment via the ``pylons.config``
     object
     """
     config = PylonsConfig()
 
     # Pylons paths
     root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-    paths = dict(root=root,
-                 controllers=os.path.join(root, 'controllers'),
-                 static_files=os.path.join(root, 'public'),
-                 templates=[os.path.join(root, 'templates')])
+    paths = dict(
+        root=root,
+        controllers=os.path.join(root, 'controllers'),
+        static_files=os.path.join(root, 'public'),
+        templates=[os.path.join(root, 'templates')]
+    )
 
     # Initialize config with the basic options
     config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
@@ -44,8 +50,11 @@
 
     config['routes.map'] = make_map(config)
     config['pylons.app_globals'] = app_globals.Globals(config)
-    config['pylons.h'] = rhodecode.lib.helpers
+    config['pylons.h'] = helpers
     rhodecode.CONFIG = config
+
+    load_rcextensions(root_path=config['here'])
+
     # Setup cache object as early as possible
     import pylons
     pylons.cache._push_object(config['pylons.app_globals'].cache)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/config/rcextensions/__init__.py	Wed Mar 28 19:54:16 2012 +0200
@@ -0,0 +1,84 @@
+# Additional mappings that are not present in the pygments lexers
+# used for building stats
+# format is {'ext':'Name'} eg. {'py':'Python'}
+# NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
+# build by pygments
+EXTRA_MAPPINGS = {}
+
+#==============================================================================
+# WHOOSH INDEX EXTENSIONS
+#==============================================================================
+# if INDEX_EXTENSIONS is [] it'll use pygments lexers extensions by default.
+# To set your own just add to this list extensions to index with content
+INDEX_EXTENSIONS = []
+
+# additional extensions for indexing besides the default from pygments
+# those get's added to INDEX_EXTENSIONS
+EXTRA_INDEX_EXTENSIONS = []
+
+
+#==============================================================================
+# POST CREATE REPOSITORY HOOK
+#==============================================================================
+# this function will be executed after each repository is created
+def _crhook(*args, **kwargs):
+    """
+    Post create repository HOOK
+    kwargs available:
+     :param repo_name:
+     :param repo_type:
+     :param description:
+     :param private:
+     :param created_on:
+     :param enable_downloads:
+     :param repo_id:
+     :param user_id:
+     :param enable_statistics:
+     :param clone_uri:
+     :param fork_id:
+     :param group_id:
+     :param created_by:
+    """
+    return 0
+CREATE_REPO_HOOK = _crhook
+
+
+#==============================================================================
+# POST PUSH HOOK
+#==============================================================================
+
+# this function will be executed after each push it's runned after the build-in
+# hook that rhodecode uses for logging pushes
+def _pushhook(*args, **kwargs):
+    """
+    Post push hook
+    kwargs available:
+
+      :param username: name of user who pushed
+      :param ip: ip of who pushed
+      :param action: pull
+      :param repository: repository name
+      :param pushed_revs: generator of pushed revisions
+    """
+    return 0
+PUSH_HOOK = _pushhook
+
+
+#==============================================================================
+# POST PULL HOOK
+#==============================================================================
+
+# this function will be executed after each push it's runned after the build-in
+# hook that rhodecode uses for logging pushes
+def _pullhook(*args, **kwargs):
+    """
+    Post pull hook
+    kwargs available::
+
+      :param username: name of user who pulled
+      :param ip: ip of who pushed
+      :param action: pull
+      :param repository: repository name
+    """
+    return 0
+PULL_HOOK = _pullhook
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/config/rcextensions/make_rcextensions.py	Wed Mar 28 19:54:16 2012 +0200
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.config.rcextensions.make_rcextensions
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Whoosh indexing module for RhodeCode
+
+    :created_on: Mar 6, 2012
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+import os
+import sys
+import pkg_resources
+import traceback
+import logging
+from os.path import dirname as dn, join as jn
+
+#to get the rhodecode import
+sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
+
+from rhodecode.lib.utils import BasePasterCommand, Command, ask_ok
+
+log = logging.getLogger(__name__)
+
+
+class MakeRcExt(BasePasterCommand):
+
+    max_args = 1
+    min_args = 1
+
+    usage = "CONFIG_FILE"
+    summary = "Creates additional extensions for rhodecode"
+    group_name = "RhodeCode"
+    takes_config_file = -1
+    parser = Command.standard_parser(verbose=True)
+
+    def command(self):
+        logging.config.fileConfig(self.path_to_ini_file)
+        from pylons import config
+
+        def _make_file(ext_file):
+            bdir = os.path.split(ext_file)[0]
+            if not os.path.isdir(bdir):
+                os.makedirs(bdir)
+            with open(ext_file, 'wb') as f:
+                f.write(tmpl)
+                log.info('Writen new extensions file to %s' % ext_file)
+
+        here = config['here']
+        tmpl = pkg_resources.resource_string(
+            'rhodecode', jn('config', 'rcextensions', '__init__.py')
+        )
+        ext_file = jn(here, 'rcextensions', '__init__.py')
+        if os.path.exists(ext_file):
+            msg = ('Extension file already exists, do you want '
+                   'to overwrite it ? [y/n]')
+            if ask_ok(msg):
+                _make_file(ext_file)
+            else:
+                log.info('nothing done...')
+        else:
+            _make_file(ext_file)
+
+    def update_parser(self):
+        pass
--- a/rhodecode/controllers/admin/repos.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/controllers/admin/repos.py	Wed Mar 28 19:54:16 2012 +0200
@@ -284,7 +284,6 @@
 
         :param repo_name:
         """
-
         try:
             RepoModel().revoke_user_permission(repo=repo_name,
                                                user=request.POST['user_id'])
--- a/rhodecode/controllers/admin/users.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/controllers/admin/users.py	Wed Mar 28 19:54:16 2012 +0200
@@ -145,11 +145,12 @@
         user_model = UserModel()
         try:
             user_model.delete(id)
+            Session.commit()
             h.flash(_('successfully deleted user'), category='success')
-            Session.commit()
         except (UserOwnsReposException, DefaultUserException), e:
-            h.flash(str(e), category='warning')
+            h.flash(e, category='warning')
         except Exception:
+            log.error(traceback.format_exc())
             h.flash(_('An error occurred during deletion of user'),
                     category='error')
         return redirect(url('users'))
--- a/rhodecode/controllers/admin/users_groups.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/controllers/admin/users_groups.py	Wed Mar 28 19:54:16 2012 +0200
@@ -32,8 +32,9 @@
 from pylons.controllers.util import abort, redirect
 from pylons.i18n.translation import _
 
+from rhodecode.lib import helpers as h
 from rhodecode.lib.exceptions import UsersGroupsAssignedException
-from rhodecode.lib import helpers as h, safe_unicode
+from rhodecode.lib.utils2 import safe_unicode
 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
 from rhodecode.lib.base import BaseController, render
 
--- a/rhodecode/controllers/api/__init__.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/controllers/api/__init__.py	Wed Mar 28 19:54:16 2012 +0200
@@ -233,10 +233,10 @@
         try:
             return json.dumps(response)
         except TypeError, e:
-            log.debug('Error encoding response: %s' % e)
+            log.error('API FAILED. Error encoding response: %s' % e)
             return json.dumps(
                 dict(
-                    self._req_id,
+                    id=self._req_id,
                     result=None,
                     error="Error encoding response"
                 )
--- a/rhodecode/controllers/api/api.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/controllers/api/api.py	Wed Mar 28 19:54:16 2012 +0200
@@ -30,16 +30,15 @@
 
 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
 from rhodecode.lib.auth import HasPermissionAllDecorator, \
-    HasPermissionAnyDecorator, PasswordGenerator
+    HasPermissionAnyDecorator, PasswordGenerator, AuthUser
 
 from rhodecode.model.meta import Session
 from rhodecode.model.scm import ScmModel
-from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
+from rhodecode.model.db import User, UsersGroup, Repository
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.user import UserModel
 from rhodecode.model.users_group import UsersGroupModel
-from rhodecode.model.repos_group import ReposGroupModel
-
+from rhodecode.lib.utils import map_groups
 
 log = logging.getLogger(__name__)
 
@@ -100,7 +99,9 @@
             email=user.email,
             active=user.active,
             admin=user.admin,
-            ldap_dn=user.ldap_dn
+            ldap_dn=user.ldap_dn,
+            last_login=user.last_login,
+            permissions=AuthUser(user_id=user.user_id).permissions
         )
 
     @HasPermissionAllDecorator('hg.admin')
@@ -122,7 +123,8 @@
                     email=user.email,
                     active=user.active,
                     admin=user.admin,
-                    ldap_dn=user.ldap_dn
+                    ldap_dn=user.ldap_dn,
+                    last_login=user.last_login,
                 )
             )
         return result
@@ -282,7 +284,7 @@
     @HasPermissionAllDecorator('hg.admin')
     def add_user_to_users_group(self, apiuser, group_name, username):
         """"
-        Add a user to a group
+        Add a user to a users group
 
         :param apiuser:
         :param group_name:
@@ -360,7 +362,7 @@
             user = user.user
             members.append(
                 dict(
-                    type_="user",
+                    type="user",
                     id=user.user_id,
                     username=user.username,
                     firstname=user.name,
@@ -377,7 +379,7 @@
             users_group = users_group.users_group
             members.append(
                 dict(
-                    type_="users_group",
+                    type="users_group",
                     id=users_group.users_group_id,
                     name=users_group.users_group_name,
                     active=users_group.users_group_active,
@@ -464,15 +466,10 @@
             if Repository.get_by_repo_name(repo_name):
                 raise JSONRPCError("repo %s already exist" % repo_name)
 
-            groups = repo_name.split('/')
+            groups = repo_name.split(Repository.url_sep())
             real_name = groups[-1]
-            groups = groups[:-1]
-            parent_id = None
-            for g in groups:
-                group = RepoGroup.get_by_group_name(g)
-                if not group:
-                    group = ReposGroupModel().create(g, '', parent_id)
-                parent_id = group.group_id
+            # create structure of groups
+            group = map_groups(repo_name)
 
             repo = RepoModel().create(
                 dict(
@@ -481,7 +478,7 @@
                     description=description,
                     private=private,
                     repo_type=repo_type,
-                    repo_group=parent_id,
+                    repo_group=group.group_id if group else None,
                     clone_uri=clone_uri
                 ),
                 owner
--- a/rhodecode/controllers/branches.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/controllers/branches.py	Wed Mar 28 19:54:16 2012 +0200
@@ -31,7 +31,7 @@
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.compat import OrderedDict
-from rhodecode.lib import safe_unicode
+from rhodecode.lib.utils2 import safe_unicode
 log = logging.getLogger(__name__)
 
 
--- a/rhodecode/controllers/changeset.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/controllers/changeset.py	Wed Mar 28 19:54:16 2012 +0200
@@ -51,13 +51,18 @@
 log = logging.getLogger(__name__)
 
 
-def anchor_url(revision, path):
+def _update_with_GET(params, GET):
+    for k in ['diff1', 'diff2', 'diff']:
+        params[k] += GET.getall(k)
+
+
+def anchor_url(revision, path, GET):
     fid = h.FID(revision, path)
-    return h.url.current(anchor=fid, **dict(request.GET))
+    return h.url.current(anchor=fid, **dict(GET))
 
 
 def get_ignore_ws(fid, GET):
-    ig_ws_global = request.GET.get('ignorews')
+    ig_ws_global = GET.get('ignorews')
     ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
     if ig_ws:
         try:
@@ -67,12 +72,13 @@
     return ig_ws_global
 
 
-def _ignorews_url(fileid=None):
-
+def _ignorews_url(GET, fileid=None):
+    fileid = str(fileid) if fileid else None
     params = defaultdict(list)
+    _update_with_GET(params, GET)
     lbl = _('show white space')
-    ig_ws = get_ignore_ws(fileid, request.GET)
-    ln_ctx = get_line_ctx(fileid, request.GET)
+    ig_ws = get_ignore_ws(fileid, GET)
+    ln_ctx = get_line_ctx(fileid, GET)
     # global option
     if fileid is None:
         if ig_ws is None:
@@ -98,7 +104,7 @@
 
 
 def get_line_ctx(fid, GET):
-    ln_ctx_global = request.GET.get('context')
+    ln_ctx_global = GET.get('context')
     ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
 
     if ln_ctx:
@@ -112,16 +118,19 @@
         return
 
 
-def _context_url(fileid=None):
+def _context_url(GET, fileid=None):
     """
     Generates url for context lines
 
     :param fileid:
     """
-    ig_ws = get_ignore_ws(fileid, request.GET)
-    ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
+
+    fileid = str(fileid) if fileid else None
+    ig_ws = get_ignore_ws(fileid, GET)
+    ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
 
     params = defaultdict(list)
+    _update_with_GET(params, GET)
 
     # global option
     if fileid is None:
@@ -162,7 +171,7 @@
         c.anchor_url = anchor_url
         c.ignorews_url = _ignorews_url
         c.context_url = _context_url
-
+        limit_off = request.GET.get('fulldiff')
         #get ranges of revisions if preset
         rev_range = revision.split('...')[:2]
         enable_comments = True
@@ -220,7 +229,7 @@
                 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
                 lim = self.cut_off_limit
                 if cumulative_diff > self.cut_off_limit:
-                    lim = -1
+                    lim = -1 if limit_off is None else None
                 size, cs1, cs2, diff, st = wrapped_diff(
                     filenode_old=None,
                     filenode_new=node,
@@ -251,7 +260,7 @@
                 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
                 lim = self.cut_off_limit
                 if cumulative_diff > self.cut_off_limit:
-                    lim = -1
+                    lim = -1 if limit_off is None else None
                 size, cs1, cs2, diff, st = wrapped_diff(
                     filenode_old=filenode_old,
                     filenode_new=node,
--- a/rhodecode/controllers/feed.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/controllers/feed.py	Wed Mar 28 19:54:16 2012 +0200
@@ -28,7 +28,7 @@
 from pylons import url, response, tmpl_context as c
 from pylons.i18n.translation import _
 
-from rhodecode.lib import safe_unicode
+from rhodecode.lib.utils2 import safe_unicode
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController
 
--- a/rhodecode/controllers/files.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/controllers/files.py	Wed Mar 28 19:54:16 2012 +0200
@@ -32,24 +32,26 @@
 from pylons.controllers.util import redirect
 from pylons.decorators import jsonify
 
-from rhodecode.lib.vcs.conf import settings
-from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
-    EmptyRepositoryError, ImproperArchiveTypeError, VCSError, \
-    NodeAlreadyExistsError
-from rhodecode.lib.vcs.nodes import FileNode
+from rhodecode.lib import diffs
+from rhodecode.lib import helpers as h
 
 from rhodecode.lib.compat import OrderedDict
-from rhodecode.lib import convert_line_endings, detect_mode, safe_str
+from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.utils import EmptyChangeset
-from rhodecode.lib import diffs
-import rhodecode.lib.helpers as h
+from rhodecode.lib.vcs.conf import settings
+from rhodecode.lib.vcs.exceptions import RepositoryError, \
+    ChangesetDoesNotExistError, EmptyRepositoryError, \
+    ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
+from rhodecode.lib.vcs.nodes import FileNode
+
 from rhodecode.model.repo import RepoModel
+from rhodecode.model.scm import ScmModel
+
 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
     _context_url, get_line_ctx, get_ignore_ws
-from rhodecode.lib.diffs import wrapped_diff
-from rhodecode.model.scm import ScmModel
+
 
 log = logging.getLogger(__name__)
 
@@ -447,7 +449,7 @@
             ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
 
             lim = request.GET.get('fulldiff') or self.cut_off_limit
-            _, cs1, cs2, diff, st = wrapped_diff(filenode_old=node1,
+            _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
                                          filenode_new=node2,
                                          cut_off_limit=lim,
                                          ignore_whitespace=ign_whitespace_lcl,
--- a/rhodecode/controllers/home.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/controllers/home.py	Wed Mar 28 19:54:16 2012 +0200
@@ -44,7 +44,7 @@
     def index(self):
         c.repos_list = self.scm_model.get_repos()
         c.groups = self.scm_model.get_repos_groups()
-
+        c.group = None
         return render('/index.html')
 
     def repo_switcher(self):
--- a/rhodecode/controllers/summary.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/controllers/summary.py	Wed Mar 28 19:54:16 2012 +0200
@@ -26,6 +26,7 @@
 import traceback
 import calendar
 import logging
+import urllib
 from time import mktime
 from datetime import timedelta, date
 from urlparse import urlparse
@@ -39,15 +40,15 @@
 
 from beaker.cache import cache_region, region_invalidate
 
+from rhodecode.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP
 from rhodecode.model.db import Statistics, CacheInvalidation
-from rhodecode.lib import ALL_READMES, ALL_EXTS
+from rhodecode.lib.utils2 import safe_unicode
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.utils import EmptyChangeset
 from rhodecode.lib.markup_renderer import MarkupRenderer
 from rhodecode.lib.celerylib import run_task
-from rhodecode.lib.celerylib.tasks import get_commits_stats, \
-    LANGUAGES_EXTENSIONS_MAP
+from rhodecode.lib.celerylib.tasks import get_commits_stats
 from rhodecode.lib.helpers import RepoPage
 from rhodecode.lib.compat import json, OrderedDict
 
@@ -91,34 +92,37 @@
 
         uri_tmpl = config.get('clone_uri', default_clone_uri)
         uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
-
+        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
         uri_dict = {
            'user': username,
            'pass': password,
            'scheme': parsed_url.scheme,
            'netloc': parsed_url.netloc,
-           'path': parsed_url.path
+           'path': decoded_path
         }
+
         uri = uri_tmpl % uri_dict
         # generate another clone url by id
-        uri_dict.update({'path': '/_%s' % c.dbrepo.repo_id})
+        uri_dict.update(
+         {'path': decoded_path.replace(repo_name, '_%s' % c.dbrepo.repo_id)}
+        )
         uri_id = uri_tmpl % uri_dict
 
         c.clone_repo_url = uri
         c.clone_repo_url_id = uri_id
         c.repo_tags = OrderedDict()
-        for name, hash in c.rhodecode_repo.tags.items()[:10]:
+        for name, hash_ in c.rhodecode_repo.tags.items()[:10]:
             try:
-                c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
+                c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash_)
             except ChangesetError:
-                c.repo_tags[name] = EmptyChangeset(hash)
+                c.repo_tags[name] = EmptyChangeset(hash_)
 
         c.repo_branches = OrderedDict()
-        for name, hash in c.rhodecode_repo.branches.items()[:10]:
+        for name, hash_ in c.rhodecode_repo.branches.items()[:10]:
             try:
-                c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
+                c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash_)
             except ChangesetError:
-                c.repo_branches[name] = EmptyChangeset(hash)
+                c.repo_branches[name] = EmptyChangeset(hash_)
 
         td = date.today() + timedelta(days=1)
         td_1m = td - timedelta(days=calendar.mdays[td.month])
@@ -175,7 +179,7 @@
         if c.enable_downloads:
             c.download_options = self._get_download_links(c.rhodecode_repo)
 
-        c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_repo)
+        c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_db_repo)
         return render('summary/summary.html')
 
     def __get_readme_data(self, repo):
@@ -206,7 +210,7 @@
 
             return readme_data, readme_file
 
-        key = repo.name + '_README'
+        key = repo.repo_name + '_README'
         inv = CacheInvalidation.invalidate(key)
         if inv is not None:
             region_invalidate(_get_readme_from_cache, None, key)
--- a/rhodecode/lib/__init__.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/__init__.py	Wed Mar 28 19:54:16 2012 +0200
@@ -1,432 +1,4 @@
-# -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.__init__
-    ~~~~~~~~~~~~~~~~~~~~~~~
-
-    Some simple helper functions
-
-    :created_on: Jan 5, 2011
-    :author: marcink
-    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
 import os
-import re
-from rhodecode.lib.vcs.utils.lazy import LazyProperty
-
-
-def __get_lem():
-    from pygments import lexers
-    from string import lower
-    from collections import defaultdict
-
-    d = defaultdict(lambda: [])
-
-    def __clean(s):
-        s = s.lstrip('*')
-        s = s.lstrip('.')
-
-        if s.find('[') != -1:
-            exts = []
-            start, stop = s.find('['), s.find(']')
-
-            for suffix in s[start + 1:stop]:
-                exts.append(s[:s.find('[')] + suffix)
-            return map(lower, exts)
-        else:
-            return map(lower, [s])
-
-    for lx, t in sorted(lexers.LEXERS.items()):
-        m = map(__clean, t[-2])
-        if m:
-            m = reduce(lambda x, y: x + y, m)
-            for ext in m:
-                desc = lx.replace('Lexer', '')
-                d[ext].append(desc)
-
-    return dict(d)
-
-# language map is also used by whoosh indexer, which for those specified
-# extensions will index it's content
-LANGUAGES_EXTENSIONS_MAP = __get_lem()
-
-# Additional mappings that are not present in the pygments lexers
-# NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
-ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
-
-LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
-
-# list of readme files to search in file tree and display in summary
-# attached weights defines the search  order lower is first
-ALL_READMES = [
-    ('readme', 0), ('README', 0), ('Readme', 0),
-    ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
-    ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
-    ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
-    ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
-]
-
-# extension together with weights to search lower is first
-RST_EXTS = [
-    ('', 0), ('.rst', 1), ('.rest', 1),
-    ('.RST', 2), ('.REST', 2),
-    ('.txt', 3), ('.TXT', 3)
-]
-
-MARKDOWN_EXTS = [
-    ('.md', 1), ('.MD', 1),
-    ('.mkdn', 2), ('.MKDN', 2),
-    ('.mdown', 3), ('.MDOWN', 3),
-    ('.markdown', 4), ('.MARKDOWN', 4)
-]
-
-PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
-
-ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
-
-
-def str2bool(_str):
-    """
-    returs True/False value from given string, it tries to translate the
-    string into boolean
-
-    :param _str: string value to translate into boolean
-    :rtype: boolean
-    :returns: boolean from given string
-    """
-    if _str is None:
-        return False
-    if _str in (True, False):
-        return _str
-    _str = str(_str).strip().lower()
-    return _str in ('t', 'true', 'y', 'yes', 'on', '1')
-
-
-def convert_line_endings(line, mode):
-    """
-    Converts a given line  "line end" accordingly to given mode
-
-    Available modes are::
-        0 - Unix
-        1 - Mac
-        2 - DOS
-
-    :param line: given line to convert
-    :param mode: mode to convert to
-    :rtype: str
-    :return: converted line according to mode
-    """
-    from string import replace
-
-    if mode == 0:
-            line = replace(line, '\r\n', '\n')
-            line = replace(line, '\r', '\n')
-    elif mode == 1:
-            line = replace(line, '\r\n', '\r')
-            line = replace(line, '\n', '\r')
-    elif mode == 2:
-            line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
-    return line
-
-
-def detect_mode(line, default):
-    """
-    Detects line break for given line, if line break couldn't be found
-    given default value is returned
-
-    :param line: str line
-    :param default: default
-    :rtype: int
-    :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
-    """
-    if line.endswith('\r\n'):
-        return 2
-    elif line.endswith('\n'):
-        return 0
-    elif line.endswith('\r'):
-        return 1
-    else:
-        return default
-
-
-def generate_api_key(username, salt=None):
-    """
-    Generates unique API key for given username, if salt is not given
-    it'll be generated from some random string
-
-    :param username: username as string
-    :param salt: salt to hash generate KEY
-    :rtype: str
-    :returns: sha1 hash from username+salt
-    """
-    from tempfile import _RandomNameSequence
-    import hashlib
-
-    if salt is None:
-        salt = _RandomNameSequence().next()
-
-    return hashlib.sha1(username + salt).hexdigest()
-
-
-def safe_unicode(str_, from_encoding=None):
-    """
-    safe unicode function. Does few trick to turn str_ into unicode
-
-    In case of UnicodeDecode error we try to return it with encoding detected
-    by chardet library if it fails fallback to unicode with errors replaced
-
-    :param str_: string to decode
-    :rtype: unicode
-    :returns: unicode object
-    """
-    if isinstance(str_, unicode):
-        return str_
-
-    if not from_encoding:
-        import rhodecode
-        DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
-        from_encoding = DEFAULT_ENCODING
-
-    try:
-        return unicode(str_)
-    except UnicodeDecodeError:
-        pass
-
-    try:
-        return unicode(str_, from_encoding)
-    except UnicodeDecodeError:
-        pass
-
-    try:
-        import chardet
-        encoding = chardet.detect(str_)['encoding']
-        if encoding is None:
-            raise Exception()
-        return str_.decode(encoding)
-    except (ImportError, UnicodeDecodeError, Exception):
-        return unicode(str_, from_encoding, 'replace')
-
-
-def safe_str(unicode_, to_encoding=None):
-    """
-    safe str function. Does few trick to turn unicode_ into string
-
-    In case of UnicodeEncodeError we try to return it with encoding detected
-    by chardet library if it fails fallback to string with errors replaced
-
-    :param unicode_: unicode to encode
-    :rtype: str
-    :returns: str object
-    """
-
-    # if it's not basestr cast to str
-    if not isinstance(unicode_, basestring):
-        return str(unicode_)
-
-    if isinstance(unicode_, str):
-        return unicode_
-
-    if not to_encoding:
-        import rhodecode
-        DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
-        to_encoding = DEFAULT_ENCODING
-
-    try:
-        return unicode_.encode(to_encoding)
-    except UnicodeEncodeError:
-        pass
-
-    try:
-        import chardet
-        encoding = chardet.detect(unicode_)['encoding']
-        print encoding
-        if encoding is None:
-            raise UnicodeEncodeError()
-
-        return unicode_.encode(encoding)
-    except (ImportError, UnicodeEncodeError):
-        return unicode_.encode(to_encoding, 'replace')
-
-    return safe_str
-
-
-def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
-    """
-    Custom engine_from_config functions that makes sure we use NullPool for
-    file based sqlite databases. This prevents errors on sqlite. This only
-    applies to sqlalchemy versions < 0.7.0
-
-    """
-    import sqlalchemy
-    from sqlalchemy import engine_from_config as efc
-    import logging
-
-    if int(sqlalchemy.__version__.split('.')[1]) < 7:
-
-        # This solution should work for sqlalchemy < 0.7.0, and should use
-        # proxy=TimerProxy() for execution time profiling
-
-        from sqlalchemy.pool import NullPool
-        url = configuration[prefix + 'url']
-
-        if url.startswith('sqlite'):
-            kwargs.update({'poolclass': NullPool})
-        return efc(configuration, prefix, **kwargs)
-    else:
-        import time
-        from sqlalchemy import event
-        from sqlalchemy.engine import Engine
-
-        log = logging.getLogger('sqlalchemy.engine')
-        BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
-        engine = efc(configuration, prefix, **kwargs)
-
-        def color_sql(sql):
-            COLOR_SEQ = "\033[1;%dm"
-            COLOR_SQL = YELLOW
-            normal = '\x1b[0m'
-            return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
-
-        if configuration['debug']:
-            #attach events only for debug configuration
-
-            def before_cursor_execute(conn, cursor, statement,
-                                    parameters, context, executemany):
-                context._query_start_time = time.time()
-                log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
-
-
-            def after_cursor_execute(conn, cursor, statement,
-                                    parameters, context, executemany):
-                total = time.time() - context._query_start_time
-                log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
-
-            event.listen(engine, "before_cursor_execute",
-                         before_cursor_execute)
-            event.listen(engine, "after_cursor_execute",
-                         after_cursor_execute)
-
-    return engine
-
-
-def age(curdate):
-    """
-    turns a datetime into an age string.
-
-    :param curdate: datetime object
-    :rtype: unicode
-    :returns: unicode words describing age
-    """
-
-    from datetime import datetime
-    from webhelpers.date import time_ago_in_words
-
-    _ = lambda s: s
-
-    if not curdate:
-        return ''
-
-    agescales = [(_(u"year"), 3600 * 24 * 365),
-                 (_(u"month"), 3600 * 24 * 30),
-                 (_(u"day"), 3600 * 24),
-                 (_(u"hour"), 3600),
-                 (_(u"minute"), 60),
-                 (_(u"second"), 1), ]
-
-    age = datetime.now() - curdate
-    age_seconds = (age.days * agescales[2][1]) + age.seconds
-    pos = 1
-    for scale in agescales:
-        if scale[1] <= age_seconds:
-            if pos == 6:
-                pos = 5
-            return '%s %s' % (time_ago_in_words(curdate,
-                                                agescales[pos][0]), _('ago'))
-        pos += 1
-
-    return _(u'just now')
-
-
-def uri_filter(uri):
-    """
-    Removes user:password from given url string
-
-    :param uri:
-    :rtype: unicode
-    :returns: filtered list of strings
-    """
-    if not uri:
-        return ''
-
-    proto = ''
-
-    for pat in ('https://', 'http://'):
-        if uri.startswith(pat):
-            uri = uri[len(pat):]
-            proto = pat
-            break
-
-    # remove passwords and username
-    uri = uri[uri.find('@') + 1:]
-
-    # get the port
-    cred_pos = uri.find(':')
-    if cred_pos == -1:
-        host, port = uri, None
-    else:
-        host, port = uri[:cred_pos], uri[cred_pos + 1:]
-
-    return filter(None, [proto, host, port])
-
-
-def credentials_filter(uri):
-    """
-    Returns a url with removed credentials
-
-    :param uri:
-    """
-
-    uri = uri_filter(uri)
-    #check if we have port
-    if len(uri) > 2 and uri[2]:
-        uri[2] = ':' + uri[2]
-
-    return ''.join(uri)
-
-
-def get_changeset_safe(repo, rev):
-    """
-    Safe version of get_changeset if this changeset doesn't exists for a
-    repo it returns a Dummy one instead
-
-    :param repo:
-    :param rev:
-    """
-    from rhodecode.lib.vcs.backends.base import BaseRepository
-    from rhodecode.lib.vcs.exceptions import RepositoryError
-    if not isinstance(repo, BaseRepository):
-        raise Exception('You must pass an Repository '
-                        'object as first argument got %s', type(repo))
-
-    try:
-        cs = repo.get_changeset(rev)
-    except RepositoryError:
-        from rhodecode.lib.utils import EmptyChangeset
-        cs = EmptyChangeset(requested_revision=rev)
-    return cs
 
 
 def get_current_revision(quiet=False):
@@ -450,16 +22,3 @@
             print ("Cannot retrieve rhodecode's revision. Original error "
                    "was: %s" % err)
         return None
-
-
-def extract_mentioned_users(s):
-    """
-    Returns unique usernames from given string s that have @mention
-
-    :param s: string to get mentions
-    """
-    usrs = {}
-    for username in re.findall(r'(?:^@|\s@)(\w+)', s):
-        usrs[username] = username
-
-    return sorted(usrs.keys())
--- a/rhodecode/lib/auth.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/auth.py	Wed Mar 28 19:54:16 2012 +0200
@@ -43,7 +43,7 @@
 if __platform__ in PLATFORM_OTHERS:
     import bcrypt
 
-from rhodecode.lib import str2bool, safe_unicode
+from rhodecode.lib.utils2 import str2bool, safe_unicode
 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
 from rhodecode.lib.auth_ldap import AuthLdap
@@ -521,8 +521,7 @@
         self.user = cls.rhodecode_user
         self.user_perms = self.user.permissions
         log.debug('checking %s permissions %s for %s %s',
-           self.__class__.__name__, self.required_perms, cls,
-               self.user)
+           self.__class__.__name__, self.required_perms, cls, self.user)
 
         if self.check_permissions():
             log.debug('Permission granted for %s %s' % (cls, self.user))
@@ -604,6 +603,7 @@
             user_perms = set([self.user_perms['repositories'][repo_name]])
         except KeyError:
             return False
+
         if self.required_perms.intersection(user_perms):
             return True
         return False
@@ -655,29 +655,37 @@
 
         for perm in perms:
             if perm not in available_perms:
-                raise Exception("'%s' permission in not defined" % perm)
+                raise Exception("'%s' permission is not defined" % perm)
         self.required_perms = set(perms)
         self.user_perms = None
-        self.granted_for = ''
         self.repo_name = None
+        self.group_name = None
 
     def __call__(self, check_Location=''):
         user = request.user
-        log.debug('checking %s %s %s', self.__class__.__name__,
-                  self.required_perms, user)
+        cls_name = self.__class__.__name__
+        check_scope = {
+            'HasPermissionAll': '',
+            'HasPermissionAny': '',
+            'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
+            'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
+            'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
+            'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
+        }.get(cls_name, '?')
+        log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
+                  self.required_perms, user, check_scope,
+                  check_Location or 'unspecified location')
         if not user:
             log.debug('Empty request user')
             return False
         self.user_perms = user.permissions
-        self.granted_for = user
-
         if self.check_permissions():
-            log.debug('Permission granted %s @ %s', self.granted_for,
+            log.debug('Permission granted for user: %s @ %s', user,
                       check_Location or 'unspecified location')
             return True
 
         else:
-            log.debug('Permission denied for %s @ %s', self.granted_for,
+            log.debug('Permission denied for user: %s @ %s', user,
                         check_Location or 'unspecified location')
             return False
 
@@ -701,7 +709,6 @@
 
 
 class HasRepoPermissionAll(PermsFunction):
-
     def __call__(self, repo_name=None, check_Location=''):
         self.repo_name = repo_name
         return super(HasRepoPermissionAll, self).__call__(check_Location)
@@ -711,19 +718,17 @@
             self.repo_name = get_repo_slug(request)
 
         try:
-            self.user_perms = set(
+            self._user_perms = set(
                 [self.user_perms['repositories'][self.repo_name]]
             )
         except KeyError:
             return False
-        self.granted_for = self.repo_name
-        if self.required_perms.issubset(self.user_perms):
+        if self.required_perms.issubset(self._user_perms):
             return True
         return False
 
 
 class HasRepoPermissionAny(PermsFunction):
-
     def __call__(self, repo_name=None, check_Location=''):
         self.repo_name = repo_name
         return super(HasRepoPermissionAny, self).__call__(check_Location)
@@ -733,13 +738,12 @@
             self.repo_name = get_repo_slug(request)
 
         try:
-            self.user_perms = set(
+            self._user_perms = set(
                 [self.user_perms['repositories'][self.repo_name]]
             )
         except KeyError:
             return False
-        self.granted_for = self.repo_name
-        if self.required_perms.intersection(self.user_perms):
+        if self.required_perms.intersection(self._user_perms):
             return True
         return False
 
@@ -751,13 +755,12 @@
 
     def check_permissions(self):
         try:
-            self.user_perms = set(
+            self._user_perms = set(
                 [self.user_perms['repositories_groups'][self.group_name]]
             )
         except KeyError:
             return False
-        self.granted_for = self.repo_name
-        if self.required_perms.intersection(self.user_perms):
+        if self.required_perms.intersection(self._user_perms):
             return True
         return False
 
@@ -769,13 +772,12 @@
 
     def check_permissions(self):
         try:
-            self.user_perms = set(
+            self._user_perms = set(
                 [self.user_perms['repositories_groups'][self.group_name]]
             )
         except KeyError:
             return False
-        self.granted_for = self.repo_name
-        if self.required_perms.issubset(self.user_perms):
+        if self.required_perms.issubset(self._user_perms):
             return True
         return False
 
@@ -788,12 +790,16 @@
         self.required_perms = set(perms)
 
     def __call__(self, user, repo_name):
+        # repo_name MUST be unicode, since we handle keys in permission
+        # dict by unicode
+        repo_name = safe_unicode(repo_name)
         usr = AuthUser(user.user_id)
         try:
             self.user_perms = set([usr.permissions['repositories'][repo_name]])
-        except:
+        except Exception:
+            log.error('Exception while accessing permissions %s' %
+                      traceback.format_exc())
             self.user_perms = set()
-        self.granted_for = ''
         self.username = user.username
         self.repo_name = repo_name
         return self.check_permissions()
@@ -803,7 +809,13 @@
                   'permissions %s for user:%s repository:%s', self.user_perms,
                                                 self.username, self.repo_name)
         if self.required_perms.intersection(self.user_perms):
-            log.debug('permission granted')
+            log.debug('permission granted for user:%s on repo:%s' % (
+                          self.username, self.repo_name
+                     )
+            )
             return True
-        log.debug('permission denied')
+        log.debug('permission denied for user:%s on repo:%s' % (
+                      self.username, self.repo_name
+                 )
+        )
         return False
--- a/rhodecode/lib/base.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/base.py	Wed Mar 28 19:54:16 2012 +0200
@@ -7,6 +7,8 @@
 import traceback
 
 from paste.auth.basic import AuthBasicAuthenticator
+from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
+from paste.httpheaders import WWW_AUTHENTICATE
 
 from pylons import config, tmpl_context as c, request, session, url
 from pylons.controllers import WSGIController
@@ -15,7 +17,7 @@
 
 from rhodecode import __version__, BACKENDS
 
-from rhodecode.lib import str2bool, safe_unicode
+from rhodecode.lib.utils2 import str2bool, safe_unicode
 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
     HasPermissionAnyMiddleware, CookieStoreWrapper
 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
@@ -28,6 +30,22 @@
 log = logging.getLogger(__name__)
 
 
+class BasicAuth(AuthBasicAuthenticator):
+
+    def __init__(self, realm, authfunc, auth_http_code=None):
+        self.realm = realm
+        self.authfunc = authfunc
+        self._rc_auth_http_code = auth_http_code
+
+    def build_authentication(self):
+        head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
+        if self._rc_auth_http_code and self._rc_auth_http_code == '403':
+            # return 403 if alternative http return code is specified in
+            # RhodeCode config
+            return HTTPForbidden(headers=head)
+        return HTTPUnauthorized(headers=head)
+
+
 class BaseVCSController(object):
 
     def __init__(self, application, config):
@@ -36,7 +54,8 @@
         # base path of repo locations
         self.basepath = self.config['base_path']
         #authenticate this mercurial request using authfunc
-        self.authenticate = AuthBasicAuthenticator('', authfunc)
+        self.authenticate = BasicAuth('', authfunc,
+                                      config.get('auth_ret_code'))
         self.ipaddr = '0.0.0.0'
 
     def _handle_request(self, environ, start_response):
--- a/rhodecode/lib/caching_query.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/caching_query.py	Wed Mar 28 19:54:16 2012 +0200
@@ -24,7 +24,7 @@
 from sqlalchemy.orm.interfaces import MapperOption
 from sqlalchemy.orm.query import Query
 from sqlalchemy.sql import visitors
-from rhodecode.lib import safe_str
+from rhodecode.lib.utils2 import safe_str
 
 
 class CachingQuery(Query):
--- a/rhodecode/lib/celerylib/__init__.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/celerylib/__init__.py	Wed Mar 28 19:54:16 2012 +0200
@@ -36,7 +36,7 @@
 
 from rhodecode.lib.vcs.utils.lazy import LazyProperty
 from rhodecode import CELERY_ON
-from rhodecode.lib import str2bool, safe_str
+from rhodecode.lib.utils2 import str2bool, safe_str
 from rhodecode.lib.pidlock import DaemonLock, LockHeld
 from rhodecode.model import init_model
 from rhodecode.model import meta
--- a/rhodecode/lib/celerylib/tasks.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/celerylib/tasks.py	Wed Mar 28 19:54:16 2012 +0200
@@ -40,7 +40,7 @@
 from rhodecode.lib.vcs import get_backend
 
 from rhodecode import CELERY_ON
-from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
+from rhodecode.lib.utils2 import safe_str
 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
     str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
 from rhodecode.lib.helpers import person
@@ -147,6 +147,7 @@
              last_rev, last_rev + parse_limit)
         )
         for cs in repo[last_rev:last_rev + parse_limit]:
+            log.debug('parsing %s' % cs)
             last_cs = cs  # remember last parsed changeset
             k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
                           cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
@@ -233,10 +234,10 @@
             lock.release()
             return False
 
-        #final release
+        # final release
         lock.release()
 
-        #execute another task if celery is enabled
+        # execute another task if celery is enabled
         if len(repo.revisions) > 1 and CELERY_ON:
             run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
         return True
@@ -327,7 +328,7 @@
     DBS = get_session()
 
     email_config = config
-    subject = "%s %s" % (email_config.get('email_prefix'), subject)
+    subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
     if not recipients:
         # if recipients are not defined we send to email_config + all admins
         admins = [u.email for u in User.query()
@@ -395,6 +396,7 @@
     DBS.commit()
 
 def __get_codes_stats(repo_name):
+    from rhodecode.config.conf import  LANGUAGES_EXTENSIONS_MAP
     repo = Repository.get_by_repo_name(repo_name).scm_instance
 
     tip = repo.get_changeset()
--- a/rhodecode/lib/celerypylons/commands.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/celerypylons/commands.py	Wed Mar 28 19:54:16 2012 +0200
@@ -1,9 +1,9 @@
 import rhodecode
-from rhodecode.lib.utils import BasePasterCommand, Command
+from rhodecode.lib.utils import BasePasterCommand, Command, load_rcextensions
 from celery.app import app_or_default
 from celery.bin import camqadm, celerybeat, celeryd, celeryev
 
-from rhodecode.lib import str2bool
+from rhodecode.lib.utils2 import str2bool
 
 __all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand',
            'CAMQPAdminCommand', 'CeleryEventCommand']
@@ -39,9 +39,11 @@
             raise Exception('Please enable celery_on in .ini config '
                             'file before running celeryd')
         rhodecode.CELERY_ON = CELERY_ON
+        load_rcextensions(config['here'])
         cmd = self.celery_command(app_or_default())
         return cmd.run(**vars(self.options))
 
+
 class CeleryDaemonCommand(CeleryCommand):
     """Start the celery worker
 
@@ -82,6 +84,7 @@
     parser = Command.standard_parser(quiet=True)
     celery_command = camqadm.AMQPAdminCommand
 
+
 class CeleryEventCommand(CeleryCommand):
     """Celery event command.
 
--- a/rhodecode/lib/compat.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/compat.py	Wed Mar 28 19:54:16 2012 +0200
@@ -25,16 +25,93 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
+import datetime
+import functools
+import decimal
 from rhodecode import __platform__, PLATFORM_WIN
 
 #==============================================================================
 # json
 #==============================================================================
+
+
+def _is_aware(value):
+    """
+    Determines if a given datetime.time is aware.
+
+    The logic is described in Python's docs:
+    http://docs.python.org/library/datetime.html#datetime.tzinfo
+    """
+    return (value.tzinfo is not None
+            and value.tzinfo.utcoffset(value) is not None)
+
+
+def _obj_dump(obj):
+    """
+    Custom function for dumping objects to JSON, if obj has __json__ attribute
+    or method defined it will be used for serialization
+
+    :param obj:
+    """
+
+    if isinstance(obj, complex):
+        return [obj.real, obj.imag]
+    # See "Date Time String Format" in the ECMA-262 specification.
+    # some code borrowed from django 1.4
+    elif isinstance(obj, datetime.datetime):
+        r = obj.isoformat()
+        if obj.microsecond:
+            r = r[:23] + r[26:]
+        if r.endswith('+00:00'):
+            r = r[:-6] + 'Z'
+        return r
+    elif isinstance(obj, datetime.date):
+        return obj.isoformat()
+    elif isinstance(obj, decimal.Decimal):
+        return str(obj)
+    elif isinstance(obj, datetime.time):
+        if _is_aware(obj):
+            raise ValueError("JSON can't represent timezone-aware times.")
+        r = obj.isoformat()
+        if obj.microsecond:
+            r = r[:12]
+        return r
+    elif isinstance(obj, set):
+        return list(obj)
+    elif isinstance(obj, OrderedDict):
+        return obj.as_dict()
+    elif hasattr(obj, '__json__'):
+        if callable(obj.__json__):
+            return obj.__json__()
+        else:
+            return obj.__json__
+    else:
+        raise NotImplementedError
+
 try:
     import json
+
+    # extended JSON encoder for json
+    class ExtendedEncoder(json.JSONEncoder):
+        def default(self, obj):
+            try:
+                return _obj_dump(obj)
+            except NotImplementedError:
+                pass
+            return json.JSONEncoder.default(self, obj)
+    # monkey-patch JSON encoder to use extended version
+    json.dumps = functools.partial(json.dumps, cls=ExtendedEncoder)
 except ImportError:
     import simplejson as json
 
+    def extended_encode(obj):
+        try:
+            return _obj_dump(obj)
+        except NotImplementedError:
+            pass
+        raise TypeError("%r is not JSON serializable" % (obj,))
+    json.dumps = functools.partial(json.dumps, default=extended_encode)
+
 
 #==============================================================================
 # izip_longest
@@ -44,11 +121,11 @@
 except ImportError:
     import itertools
 
-    def izip_longest(*args, **kwds): # noqa
+    def izip_longest(*args, **kwds):
         fillvalue = kwds.get("fillvalue")
 
         def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
-            yield counter() # yields the fillvalue, or raises IndexError
+            yield counter()  # yields the fillvalue, or raises IndexError
 
         fillers = itertools.repeat(fillvalue)
         iters = [itertools.chain(it, sentinel(), fillers)
--- a/rhodecode/lib/db_manage.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/db_manage.py	Wed Mar 28 19:54:16 2012 +0200
@@ -376,7 +376,7 @@
 
         if not self.tests and not test_repo_path:
             path = raw_input(
-                 'Enter a valid path to store repositories. '
+                 'Enter a valid absolute path to store repositories. '
                  'All repositories in that path will be added automatically:'
             )
         else:
@@ -388,8 +388,12 @@
             path_ok = False
             log.error('Given path %s is not a valid directory' % path)
 
+        elif not os.path.isabs(path):
+            path_ok = False
+            log.error('Given path %s is not an absolute path' % path)
+
         # check write access
-        if not os.access(path, os.W_OK) and path_ok:
+        elif not os.access(path, os.W_OK) and path_ok:
             path_ok = False
             log.error('No write permission to given path %s' % path)
 
--- a/rhodecode/lib/dbmigrate/migrate/exceptions.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/exceptions.py	Wed Mar 28 19:54:16 2012 +0200
@@ -71,9 +71,6 @@
     """Invalid script error."""
 
 
-class InvalidVersionError(Error):
-    """Invalid version error."""
-
 # migrate.changeset
 
 class NotSupportedError(Error):
--- a/rhodecode/lib/dbmigrate/schema/db_1_2_0.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py	Wed Mar 28 19:54:16 2012 +0200
@@ -39,7 +39,7 @@
 from rhodecode.lib.vcs.exceptions import VCSError
 from rhodecode.lib.vcs.utils.lazy import LazyProperty
 
-from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
+from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
     generate_api_key, safe_unicode
 from rhodecode.lib.exceptions import UsersGroupsAssignedException
 from rhodecode.lib.compat import json
@@ -717,7 +717,7 @@
         return repo
 
 
-class RepoGroup(Base, BaseModel):
+class Group(Base, BaseModel):
     __tablename__ = 'groups'
     __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
                       CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
@@ -728,8 +728,7 @@
     group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
     group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-
+    parent_group = relationship('Group', remote_side=group_id)
 
     def __init__(self, group_name='', parent_group=None):
         self.group_name = group_name
--- a/rhodecode/lib/helpers.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/helpers.py	Wed Mar 28 19:54:16 2012 +0200
@@ -39,12 +39,20 @@
 
 from rhodecode.lib.annotate import annotate_highlight
 from rhodecode.lib.utils import repo_name_slug
-from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
+from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
+    get_changeset_safe
 from rhodecode.lib.markup_renderer import MarkupRenderer
 
 log = logging.getLogger(__name__)
 
 
+def shorter(text, size=20):
+    postfix = '...'
+    if len(text) > size:
+        return text[:size - len(postfix)] + postfix
+    return text
+
+
 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
     """
     Reset button
@@ -67,7 +75,7 @@
     :param path:
     """
 
-    return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
+    return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
 
 
 def get_token():
@@ -86,6 +94,7 @@
             session.save()
     return session[token_key]
 
+
 class _GetError(object):
     """Get error from form_errors, and represent it as span wrapped error
     message
@@ -101,6 +110,7 @@
 
 get_error = _GetError()
 
+
 class _ToolTip(object):
 
     def __call__(self, tooltip_title, trim_at=50):
@@ -112,6 +122,7 @@
         return escape(tooltip_title)
 tooltip = _ToolTip()
 
+
 class _FilesBreadCrumbs(object):
 
     def __call__(self, repo_name, rev, paths):
@@ -136,8 +147,10 @@
 
 files_breadcrumbs = _FilesBreadCrumbs()
 
+
 class CodeHtmlFormatter(HtmlFormatter):
-    """My code Html Formatter for source codes
+    """
+    My code Html Formatter for source codes
     """
 
     def wrap(self, source, outfile):
@@ -319,7 +332,7 @@
 # SCM FILTERS available via h.
 #==============================================================================
 from rhodecode.lib.vcs.utils import author_name, author_email
-from rhodecode.lib import credentials_filter, age as _age
+from rhodecode.lib.utils2 import credentials_filter, age as _age
 from rhodecode.model.db import User
 
 age = lambda  x: _age(x)
@@ -759,10 +772,10 @@
     d_v = d if d > 0 else ''
 
     def cgen(l_type):
-        mapping = {'tr': 'top-right-rounded-corner',
-                   'tl': 'top-left-rounded-corner',
-                   'br': 'bottom-right-rounded-corner',
-                   'bl': 'bottom-left-rounded-corner'}
+        mapping = {'tr': 'top-right-rounded-corner-mid',
+                   'tl': 'top-left-rounded-corner-mid',
+                   'br': 'bottom-right-rounded-corner-mid',
+                   'bl': 'bottom-left-rounded-corner-mid'}
         map_getter = lambda x: mapping[x]
 
         if l_type == 'a' and d_v:
@@ -801,6 +814,12 @@
 
 
 def urlify_changesets(text_, repository):
+    """
+    Extract revision ids from changeset and make link from them
+
+    :param text_:
+    :param repository:
+    """
     import re
     URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
 
@@ -839,8 +858,8 @@
     import re
     import traceback
 
-    # urlify changesets
-    text_ = urlify_changesets(text_, repository)
+    def escaper(string):
+        return string.replace('<', '&lt;').replace('>', '&gt;')
 
     def linkify_others(t, l):
         urls = re.compile(r'(\<a.*?\<\/a\>)',)
@@ -852,6 +871,11 @@
                 links.append(e)
 
         return ''.join(links)
+
+
+    # urlify changesets - extrac revisions and make link out of them
+    text_ = urlify_changesets(escaper(text_), repository)
+
     try:
         conf = config['app_conf']
 
--- a/rhodecode/lib/hooks.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/hooks.py	Wed Mar 28 19:54:16 2012 +0200
@@ -27,9 +27,10 @@
 
 from mercurial.scmutil import revrange
 from mercurial.node import nullrev
-
+from rhodecode import EXTENSIONS
 from rhodecode.lib import helpers as h
 from rhodecode.lib.utils import action_logger
+from inspect import isfunction
 
 
 def repo_size(ui, repo, hooktype=None, **kwargs):
@@ -78,14 +79,19 @@
     :param repo:
     """
 
-    extra_params = dict(repo.ui.configitems('rhodecode_extras'))
-    username = extra_params['username']
-    repository = extra_params['repository']
+    extras = dict(repo.ui.configitems('rhodecode_extras'))
+    username = extras['username']
+    repository = extras['repository']
     action = 'pull'
 
-    action_logger(username, action, repository, extra_params['ip'],
-                  commit=True)
+    action_logger(username, action, repository, extras['ip'], commit=True)
+    # extension hook call
+    callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
 
+    if isfunction(callback):
+        kw = {}
+        kw.update(extras)
+        callback(**kw)
     return 0
 
 
@@ -97,10 +103,10 @@
     :param repo:
     """
 
-    extra_params = dict(repo.ui.configitems('rhodecode_extras'))
-    username = extra_params['username']
-    repository = extra_params['repository']
-    action = extra_params['action'] + ':%s'
+    extras = dict(repo.ui.configitems('rhodecode_extras'))
+    username = extras['username']
+    repository = extras['repository']
+    action = extras['action'] + ':%s'
     node = kwargs['node']
 
     def get_revs(repo, rev_opt):
@@ -119,16 +125,22 @@
 
     action = action % ','.join(revs)
 
-    action_logger(username, action, repository, extra_params['ip'],
-                  commit=True)
+    action_logger(username, action, repository, extras['ip'], commit=True)
 
+    # extension hook call
+    callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
+    if isfunction(callback):
+        kw = {'pushed_revs': revs}
+        kw.update(extras)
+        callback(**kw)
     return 0
 
 
 def log_create_repository(repository_dict, created_by, **kwargs):
     """
     Post create repository Hook. This is a dummy function for admins to re-use
-    if needed
+    if needed. It's taken from rhodecode-extensions module and executed
+    if present
 
     :param repository: dict dump of repository object
     :param created_by: username who created repository
@@ -151,5 +163,12 @@
 
     """
 
+    callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
+    if isfunction(callback):
+        kw = {}
+        kw.update(repository_dict)
+        kw.update({'created_by': created_by})
+        kw.update(kwargs)
+        return callback(**kw)
 
     return 0
--- a/rhodecode/lib/indexers/__init__.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/indexers/__init__.py	Wed Mar 28 19:54:16 2012 +0200
@@ -25,6 +25,7 @@
 import os
 import sys
 import traceback
+import logging
 from os.path import dirname as dn, join as jn
 
 #to get the rhodecode import
@@ -46,11 +47,9 @@
 from rhodecode.model.scm import ScmModel
 from rhodecode.model.repo import RepoModel
 from rhodecode.config.environment import load_environment
-from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, LazyProperty
-from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
-
-# EXTENSIONS WE WANT TO INDEX CONTENT OFF
-INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
+from rhodecode.lib.utils2 import LazyProperty
+from rhodecode.lib.utils import BasePasterCommand, Command, add_cache,\
+    load_rcextensions
 
 # CUSTOM ANALYZER wordsplit + lowercase filter
 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
@@ -84,18 +83,17 @@
     parser = Command.standard_parser(verbose=True)
 
     def command(self):
-
+        logging.config.fileConfig(self.path_to_ini_file)
         from pylons import config
         add_cache(config)
         engine = engine_from_config(config, 'sqlalchemy.db1.')
         init_model(engine)
-
         index_location = config['index_dir']
         repo_location = self.options.repo_location \
             if self.options.repo_location else RepoModel().repos_path
         repo_list = map(strip, self.options.repo_list.split(',')) \
             if self.options.repo_list else None
-
+        load_rcextensions(config['here'])
         #======================================================================
         # WHOOSH DAEMON
         #======================================================================
@@ -105,7 +103,7 @@
             l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock'))
             WhooshIndexingDaemon(index_location=index_location,
                                  repo_location=repo_location,
-                                 repo_list=repo_list)\
+                                 repo_list=repo_list,)\
                 .run(full_index=self.options.full_index)
             l.release()
         except LockHeld:
--- a/rhodecode/lib/indexers/daemon.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/indexers/daemon.py	Wed Mar 28 19:54:16 2012 +0200
@@ -38,34 +38,17 @@
 project_path = dn(dn(dn(dn(os.path.realpath(__file__)))))
 sys.path.append(project_path)
 
-
+from rhodecode.config.conf import INDEX_EXTENSIONS
 from rhodecode.model.scm import ScmModel
-from rhodecode.lib import safe_unicode
-from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME
+from rhodecode.lib.utils2 import safe_unicode
+from rhodecode.lib.indexers import SCHEMA, IDX_NAME
 
 from rhodecode.lib.vcs.exceptions import ChangesetError, RepositoryError, \
     NodeDoesNotExistError
 
 from whoosh.index import create_in, open_dir
 
-
-log = logging.getLogger('whooshIndexer')
-# create logger
-log.setLevel(logging.DEBUG)
-log.propagate = False
-# create console handler and set level to debug
-ch = logging.StreamHandler()
-ch.setLevel(logging.DEBUG)
-
-# create formatter
-formatter = logging.Formatter("%(asctime)s - %(name)s -"
-                              " %(levelname)s - %(message)s")
-
-# add formatter to ch
-ch.setFormatter(formatter)
-
-# add ch to logger
-log.addHandler(ch)
+log = logging.getLogger('whoosh_indexer')
 
 
 class WhooshIndexingDaemon(object):
@@ -103,7 +86,8 @@
             self.initial = True
 
     def get_paths(self, repo):
-        """recursive walk in root dir and return a set of all path in that dir
+        """
+        recursive walk in root dir and return a set of all path in that dir
         based on repository walk function
         """
         index_paths_ = set()
@@ -127,32 +111,39 @@
         return mktime(node.last_changeset.date.timetuple())
 
     def add_doc(self, writer, path, repo, repo_name):
-        """Adding doc to writer this function itself fetches data from
-        the instance of vcs backend"""
-        node = self.get_node(repo, path)
+        """
+        Adding doc to writer this function itself fetches data from
+        the instance of vcs backend
+        """
 
-        #we just index the content of chosen files, and skip binary files
+        node = self.get_node(repo, path)
+        indexed = indexed_w_content = 0
+        # we just index the content of chosen files, and skip binary files
         if node.extension in INDEX_EXTENSIONS and not node.is_binary:
-
             u_content = node.content
             if not isinstance(u_content, unicode):
                 log.warning('  >> %s Could not get this content as unicode '
-                          'replacing with empty content', path)
+                            'replacing with empty content' % path)
                 u_content = u''
             else:
                 log.debug('    >> %s [WITH CONTENT]' % path)
+                indexed_w_content += 1
 
         else:
             log.debug('    >> %s' % path)
-            #just index file name without it's content
+            # just index file name without it's content
             u_content = u''
+            indexed += 1
 
-        writer.add_document(owner=unicode(repo.contact),
-                        repository=safe_unicode(repo_name),
-                        path=safe_unicode(path),
-                        content=u_content,
-                        modtime=self.get_node_mtime(node),
-                        extension=node.extension)
+        writer.add_document(
+            owner=unicode(repo.contact),
+            repository=safe_unicode(repo_name),
+            path=safe_unicode(path),
+            content=u_content,
+            modtime=self.get_node_mtime(node),
+            extension=node.extension
+        )
+        return indexed, indexed_w_content
 
     def build_index(self):
         if os.path.exists(self.index_location):
@@ -164,19 +155,25 @@
 
         idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME)
         writer = idx.writer()
-
+        log.debug('BUILDIN INDEX FOR EXTENSIONS %s' % INDEX_EXTENSIONS)
         for repo_name, repo in self.repo_paths.items():
             log.debug('building index @ %s' % repo.path)
-
+            i_cnt = iwc_cnt = 0
             for idx_path in self.get_paths(repo):
-                self.add_doc(writer, idx_path, repo, repo_name)
+                i, iwc = self.add_doc(writer, idx_path, repo, repo_name)
+                i_cnt += i
+                iwc_cnt += iwc
+            log.debug('added %s files %s with content for repo %s' % (
+                         i_cnt + iwc_cnt, iwc_cnt, repo.path)
+            )
 
         log.debug('>> COMMITING CHANGES <<')
         writer.commit(merge=True)
         log.debug('>>> FINISHED BUILDING INDEX <<<')
 
     def update_index(self):
-        log.debug('STARTING INCREMENTAL INDEXING UPDATE')
+        log.debug('STARTING INCREMENTAL INDEXING UPDATE FOR EXTENSIONS %s' %
+                  INDEX_EXTENSIONS)
 
         idx = open_dir(self.index_location, indexname=self.indexname)
         # The set of all paths in the index
@@ -215,14 +212,19 @@
         # Loop over the files in the filesystem
         # Assume we have a function that gathers the filenames of the
         # documents to be indexed
+        ri_cnt = riwc_cnt = 0
         for repo_name, repo in self.repo_paths.items():
             for path in self.get_paths(repo):
                 if path in to_index or path not in indexed_paths:
                     # This is either a file that's changed, or a new file
                     # that wasn't indexed before. So index it!
-                    self.add_doc(writer, path, repo, repo_name)
+                    i, iwc = self.add_doc(writer, path, repo, repo_name)
                     log.debug('re indexing %s' % path)
-
+                    ri_cnt += i
+                    riwc_cnt += iwc
+        log.debug('added %s files %s with content for repo %s' % (
+                     ri_cnt + riwc_cnt, riwc_cnt, repo.path)
+        )
         log.debug('>> COMMITING CHANGES <<')
         writer.commit(merge=True)
         log.debug('>>> FINISHED REBUILDING INDEX <<<')
--- a/rhodecode/lib/markup_renderer.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/markup_renderer.py	Wed Mar 28 19:54:16 2012 +0200
@@ -27,7 +27,7 @@
 import re
 import logging
 
-from rhodecode.lib import safe_unicode
+from rhodecode.lib.utils2 import safe_unicode
 
 log = logging.getLogger(__name__)
 
--- a/rhodecode/lib/middleware/https_fixup.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/middleware/https_fixup.py	Wed Mar 28 19:54:16 2012 +0200
@@ -23,7 +23,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from rhodecode.lib import str2bool
+from rhodecode.lib.utils2 import str2bool
 
 
 class HttpsFixup(object):
--- a/rhodecode/lib/middleware/simplegit.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/middleware/simplegit.py	Wed Mar 28 19:54:16 2012 +0200
@@ -65,11 +65,11 @@
 }
 
 from dulwich.repo import Repo
-from dulwich.web import HTTPGitApplication
+from dulwich.web import make_wsgi_chain
 
 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 
-from rhodecode.lib import safe_str
+from rhodecode.lib.utils2 import safe_str
 from rhodecode.lib.base import BaseVCSController
 from rhodecode.lib.auth import get_container_username
 from rhodecode.lib.utils import is_valid_repo
@@ -86,7 +86,9 @@
 def is_git(environ):
     path_info = environ['PATH_INFO']
     isgit_path = GIT_PROTO_PAT.match(path_info)
-    log.debug('is a git path %s pathinfo : %s' % (isgit_path, path_info))
+    log.debug('pathinfo: %s detected as GIT %s' % (
+        path_info, isgit_path != None)
+    )
     return isgit_path
 
 
@@ -113,6 +115,10 @@
         except:
             return HTTPInternalServerError()(environ, start_response)
 
+        # quick check if that dir exists...
+        if is_valid_repo(repo_name, self.basepath) is False:
+            return HTTPNotFound()(environ, start_response)
+
         #======================================================================
         # GET ACTION PULL or PUSH
         #======================================================================
@@ -121,7 +127,6 @@
         #======================================================================
         # CHECK ANONYMOUS PERMISSION
         #======================================================================
-
         if action in ['pull', 'push']:
             anonymous_user = self.__get_user('default')
             username = anonymous_user.username
@@ -177,13 +182,9 @@
         #===================================================================
         # GIT REQUEST HANDLING
         #===================================================================
-        repo_path = safe_str(os.path.join(self.basepath, repo_name))
+        repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
         log.debug('Repository path is %s' % repo_path)
 
-        # quick check if that dir exists...
-        if is_valid_repo(repo_name, self.basepath) is False:
-            return HTTPNotFound()(environ, start_response)
-
         try:
             #invalidate cache on push
             if action == 'push':
@@ -204,7 +205,7 @@
         """
         _d = {'/' + repo_name: Repo(repo_path)}
         backend = dulserver.DictBackend(_d)
-        gitserve = HTTPGitApplication(backend)
+        gitserve = make_wsgi_chain(backend)
 
         return gitserve
 
--- a/rhodecode/lib/middleware/simplehg.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/middleware/simplehg.py	Wed Mar 28 19:54:16 2012 +0200
@@ -27,13 +27,14 @@
 import os
 import logging
 import traceback
+import urllib
 
 from mercurial.error import RepoError
 from mercurial.hgweb import hgweb_mod
 
 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 
-from rhodecode.lib import safe_str
+from rhodecode.lib.utils2 import safe_str
 from rhodecode.lib.base import BaseVCSController
 from rhodecode.lib.auth import get_container_username
 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
@@ -45,13 +46,21 @@
 
 
 def is_mercurial(environ):
-    """Returns True if request's target is mercurial server - header
+    """
+    Returns True if request's target is mercurial server - header
     ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
     """
     http_accept = environ.get('HTTP_ACCEPT')
+    path_info = environ['PATH_INFO']
     if http_accept and http_accept.startswith('application/mercurial'):
-        return True
-    return False
+        ishg_path = True
+    else:
+        ishg_path = False
+
+    log.debug('pathinfo: %s detected as HG %s' % (
+        path_info, ishg_path)
+    )
+    return ishg_path
 
 
 class SimpleHg(BaseVCSController):
@@ -76,16 +85,20 @@
         except:
             return HTTPInternalServerError()(environ, start_response)
 
+        # quick check if that dir exists...
+        if is_valid_repo(repo_name, self.basepath) is False:
+            return HTTPNotFound()(environ, start_response)
+
         #======================================================================
         # GET ACTION PULL or PUSH
         #======================================================================
         action = self.__get_action(environ)
+
         #======================================================================
         # CHECK ANONYMOUS PERMISSION
         #======================================================================
         if action in ['pull', 'push']:
             anonymous_user = self.__get_user('default')
-
             username = anonymous_user.username
             anonymous_perm = self._check_permission(action, anonymous_user,
                                                     repo_name)
@@ -132,30 +145,28 @@
                                                          start_response)
 
                     #check permissions for this repository
-                    perm = self._check_permission(action, user,
-                                                   repo_name)
+                    perm = self._check_permission(action, user, repo_name)
                     if perm is not True:
                         return HTTPForbidden()(environ, start_response)
 
-        extras = {'ip': ipaddr,
-                  'username': username,
-                  'action': action,
-                  'repository': repo_name}
+        # extras are injected into mercurial UI object and later available
+        # in hg hooks executed by rhodecode
+        extras = {
+            'ip': ipaddr,
+            'username': username,
+            'action': action,
+            'repository': repo_name
+        }
 
         #======================================================================
         # MERCURIAL REQUEST HANDLING
         #======================================================================
-
-        repo_path = safe_str(os.path.join(self.basepath, repo_name))
+        repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
         log.debug('Repository path is %s' % repo_path)
 
         baseui = make_ui('db')
         self.__inject_extras(repo_path, baseui, extras)
 
-        # quick check if that dir exists...
-        if is_valid_repo(repo_name, self.basepath) is False:
-            return HTTPNotFound()(environ, start_response)
-
         try:
             # invalidate cache on push
             if action == 'push':
--- a/rhodecode/lib/utils.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/utils.py	Wed Mar 28 19:54:16 2012 +0200
@@ -51,9 +51,12 @@
 
 from rhodecode.model import meta
 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
-    UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
+    UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm,\
+    CacheInvalidation
 from rhodecode.model.meta import Session
 from rhodecode.model.repos_group import ReposGroupModel
+from rhodecode.lib.utils2 import safe_str, safe_unicode
+from rhodecode.lib.vcs.utils.fakemod import create_module
 
 log = logging.getLogger(__name__)
 
@@ -61,7 +64,8 @@
 
 
 def recursive_replace(str_, replace=' '):
-    """Recursive replace of given sign to just one instance
+    """
+    Recursive replace of given sign to just one instance
 
     :param str_: given string
     :param replace: char to find and replace multiple instances
@@ -79,7 +83,8 @@
 
 
 def repo_name_slug(value):
-    """Return slug of name of repository
+    """
+    Return slug of name of repository
     This function is called on each creation/modification
     of repository to prevent bad names in repo
     """
@@ -154,7 +159,10 @@
         user_log.user_ip = ipaddr
         sa.add(user_log)
 
-        log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
+        log.info(
+            'Adding user %s, action %s on %s' % (user_obj, action,
+                                                 safe_unicode(repo))
+        )
         if commit:
             sa.commit()
     except:
@@ -198,12 +206,13 @@
 def is_valid_repo(repo_name, base_path):
     """
     Returns True if given path is a valid repository False otherwise
+
     :param repo_name:
     :param base_path:
 
     :return True: if given path is a valid repository
     """
-    full_path = os.path.join(base_path, repo_name)
+    full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
 
     try:
         get_scm(full_path)
@@ -219,7 +228,7 @@
     :param repo_name:
     :param base_path:
     """
-    full_path = os.path.join(base_path, repos_group_name)
+    full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
 
     # check if it's not a repo
     if is_valid_repo(repos_group_name, base_path):
@@ -258,7 +267,8 @@
 
 
 def make_ui(read_from='file', path=None, checkpaths=True):
-    """A function that will read python rc files or database
+    """
+    A function that will read python rc files or database
     and make an mercurial ui object from read options
 
     :param path: path to mercurial config file
@@ -371,15 +381,16 @@
         return 0
 
 
-def map_groups(groups):
+def map_groups(path):
     """
-    Checks for groups existence, and creates groups structures.
-    It returns last group in structure
+    Given a full path to a repository, create all nested groups that this
+    repo is inside. This function creates parent-child relationships between
+    groups and creates default perms for all new groups.
 
-    :param groups: list of groups structure
+    :param paths: full path to repository
     """
     sa = meta.Session
-
+    groups = path.split(Repository.url_sep())
     parent = None
     group = None
 
@@ -391,22 +402,18 @@
         group = RepoGroup.get_by_group_name(group_name)
         desc = '%s group' % group_name
 
-#        # WTF that doesn't work !?
-#        if group is None:
-#            group = rgm.create(group_name, desc, parent, just_db=True)
-#            sa.commit()
-
         # skip folders that are now removed repos
         if REMOVED_REPO_PAT.match(group_name):
             break
 
         if group is None:
-            log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
+            log.debug('creating group level: %s group_name: %s' % (lvl,
+                                                                   group_name))
             group = RepoGroup(group_name, parent)
             group.group_description = desc
             sa.add(group)
             rgm._create_default_perms(group)
-            sa.commit()
+            sa.flush()
         parent = group
     return group
 
@@ -429,7 +436,7 @@
     added = []
 
     for name, repo in initial_repo_list.items():
-        group = map_groups(name.split(Repository.url_sep()))
+        group = map_groups(name)
         if not rm.get_by_repo_name(name, cache=False):
             log.info('repository %s not found creating default' % name)
             added.append(name)
@@ -446,13 +453,19 @@
     sa.commit()
     removed = []
     if remove_obsolete:
-        #remove from database those repositories that are not in the filesystem
+        # remove from database those repositories that are not in the filesystem
         for repo in sa.query(Repository).all():
             if repo.repo_name not in initial_repo_list.keys():
+                log.debug("Removing non existing repository found in db %s" %
+                          repo.repo_name)
                 removed.append(repo.repo_name)
                 sa.delete(repo)
                 sa.commit()
 
+    # clear cache keys
+    log.debug("Clearing cache keys now...")
+    CacheInvalidation.clear_cache()
+    sa.commit()
     return added, removed
 
 
@@ -484,6 +497,30 @@
             beaker.cache.cache_regions[region] = region_settings
 
 
+def load_rcextensions(root_path):
+    import rhodecode
+    from rhodecode.config import conf
+
+    path = os.path.join(root_path, 'rcextensions', '__init__.py')
+    if os.path.isfile(path):
+        rcext = create_module('rc', path)
+        EXT = rhodecode.EXTENSIONS = rcext
+        log.debug('Found rcextensions now loading %s...' % rcext)
+
+        # Additional mappings that are not present in the pygments lexers
+        conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
+
+        #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
+
+        if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
+            log.debug('settings custom INDEX_EXTENSIONS')
+            conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
+
+        #ADDITIONAL MAPPINGS
+        log.debug('adding extra into INDEX_EXTENSIONS')
+        conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
+
+
 #==============================================================================
 # TEST FUNCTIONS AND CREATORS
 #==============================================================================
@@ -624,6 +661,6 @@
         """
         from pylons import config as pylonsconfig
 
-        path_to_ini_file = os.path.realpath(conf)
-        conf = paste.deploy.appconfig('config:' + path_to_ini_file)
+        self.path_to_ini_file = os.path.realpath(conf)
+        conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
         pylonsconfig.init_app(conf.global_conf, conf.local_conf)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/utils2.py	Wed Mar 28 19:54:16 2012 +0200
@@ -0,0 +1,405 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.lib.utils
+    ~~~~~~~~~~~~~~~~~~~
+
+    Some simple helper functions
+
+    :created_on: Jan 5, 2011
+    :author: marcink
+    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import re
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+
+
+def __get_lem():
+    """
+    Get language extension map based on what's inside pygments lexers
+    """
+    from pygments import lexers
+    from string import lower
+    from collections import defaultdict
+
+    d = defaultdict(lambda: [])
+
+    def __clean(s):
+        s = s.lstrip('*')
+        s = s.lstrip('.')
+
+        if s.find('[') != -1:
+            exts = []
+            start, stop = s.find('['), s.find(']')
+
+            for suffix in s[start + 1:stop]:
+                exts.append(s[:s.find('[')] + suffix)
+            return map(lower, exts)
+        else:
+            return map(lower, [s])
+
+    for lx, t in sorted(lexers.LEXERS.items()):
+        m = map(__clean, t[-2])
+        if m:
+            m = reduce(lambda x, y: x + y, m)
+            for ext in m:
+                desc = lx.replace('Lexer', '')
+                d[ext].append(desc)
+
+    return dict(d)
+
+def str2bool(_str):
+    """
+    returs True/False value from given string, it tries to translate the
+    string into boolean
+
+    :param _str: string value to translate into boolean
+    :rtype: boolean
+    :returns: boolean from given string
+    """
+    if _str is None:
+        return False
+    if _str in (True, False):
+        return _str
+    _str = str(_str).strip().lower()
+    return _str in ('t', 'true', 'y', 'yes', 'on', '1')
+
+
+def convert_line_endings(line, mode):
+    """
+    Converts a given line  "line end" accordingly to given mode
+
+    Available modes are::
+        0 - Unix
+        1 - Mac
+        2 - DOS
+
+    :param line: given line to convert
+    :param mode: mode to convert to
+    :rtype: str
+    :return: converted line according to mode
+    """
+    from string import replace
+
+    if mode == 0:
+            line = replace(line, '\r\n', '\n')
+            line = replace(line, '\r', '\n')
+    elif mode == 1:
+            line = replace(line, '\r\n', '\r')
+            line = replace(line, '\n', '\r')
+    elif mode == 2:
+            line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
+    return line
+
+
+def detect_mode(line, default):
+    """
+    Detects line break for given line, if line break couldn't be found
+    given default value is returned
+
+    :param line: str line
+    :param default: default
+    :rtype: int
+    :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
+    """
+    if line.endswith('\r\n'):
+        return 2
+    elif line.endswith('\n'):
+        return 0
+    elif line.endswith('\r'):
+        return 1
+    else:
+        return default
+
+
+def generate_api_key(username, salt=None):
+    """
+    Generates unique API key for given username, if salt is not given
+    it'll be generated from some random string
+
+    :param username: username as string
+    :param salt: salt to hash generate KEY
+    :rtype: str
+    :returns: sha1 hash from username+salt
+    """
+    from tempfile import _RandomNameSequence
+    import hashlib
+
+    if salt is None:
+        salt = _RandomNameSequence().next()
+
+    return hashlib.sha1(username + salt).hexdigest()
+
+
+def safe_unicode(str_, from_encoding=None):
+    """
+    safe unicode function. Does few trick to turn str_ into unicode
+
+    In case of UnicodeDecode error we try to return it with encoding detected
+    by chardet library if it fails fallback to unicode with errors replaced
+
+    :param str_: string to decode
+    :rtype: unicode
+    :returns: unicode object
+    """
+    if isinstance(str_, unicode):
+        return str_
+
+    if not from_encoding:
+        import rhodecode
+        DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
+        from_encoding = DEFAULT_ENCODING
+
+    try:
+        return unicode(str_)
+    except UnicodeDecodeError:
+        pass
+
+    try:
+        return unicode(str_, from_encoding)
+    except UnicodeDecodeError:
+        pass
+
+    try:
+        import chardet
+        encoding = chardet.detect(str_)['encoding']
+        if encoding is None:
+            raise Exception()
+        return str_.decode(encoding)
+    except (ImportError, UnicodeDecodeError, Exception):
+        return unicode(str_, from_encoding, 'replace')
+
+
+def safe_str(unicode_, to_encoding=None):
+    """
+    safe str function. Does few trick to turn unicode_ into string
+
+    In case of UnicodeEncodeError we try to return it with encoding detected
+    by chardet library if it fails fallback to string with errors replaced
+
+    :param unicode_: unicode to encode
+    :rtype: str
+    :returns: str object
+    """
+
+    # if it's not basestr cast to str
+    if not isinstance(unicode_, basestring):
+        return str(unicode_)
+
+    if isinstance(unicode_, str):
+        return unicode_
+
+    if not to_encoding:
+        import rhodecode
+        DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
+        to_encoding = DEFAULT_ENCODING
+
+    try:
+        return unicode_.encode(to_encoding)
+    except UnicodeEncodeError:
+        pass
+
+    try:
+        import chardet
+        encoding = chardet.detect(unicode_)['encoding']
+        print encoding
+        if encoding is None:
+            raise UnicodeEncodeError()
+
+        return unicode_.encode(encoding)
+    except (ImportError, UnicodeEncodeError):
+        return unicode_.encode(to_encoding, 'replace')
+
+    return safe_str
+
+
+def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
+    """
+    Custom engine_from_config functions that makes sure we use NullPool for
+    file based sqlite databases. This prevents errors on sqlite. This only
+    applies to sqlalchemy versions < 0.7.0
+
+    """
+    import sqlalchemy
+    from sqlalchemy import engine_from_config as efc
+    import logging
+
+    if int(sqlalchemy.__version__.split('.')[1]) < 7:
+
+        # This solution should work for sqlalchemy < 0.7.0, and should use
+        # proxy=TimerProxy() for execution time profiling
+
+        from sqlalchemy.pool import NullPool
+        url = configuration[prefix + 'url']
+
+        if url.startswith('sqlite'):
+            kwargs.update({'poolclass': NullPool})
+        return efc(configuration, prefix, **kwargs)
+    else:
+        import time
+        from sqlalchemy import event
+        from sqlalchemy.engine import Engine
+
+        log = logging.getLogger('sqlalchemy.engine')
+        BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
+        engine = efc(configuration, prefix, **kwargs)
+
+        def color_sql(sql):
+            COLOR_SEQ = "\033[1;%dm"
+            COLOR_SQL = YELLOW
+            normal = '\x1b[0m'
+            return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
+
+        if configuration['debug']:
+            #attach events only for debug configuration
+
+            def before_cursor_execute(conn, cursor, statement,
+                                    parameters, context, executemany):
+                context._query_start_time = time.time()
+                log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
+
+
+            def after_cursor_execute(conn, cursor, statement,
+                                    parameters, context, executemany):
+                total = time.time() - context._query_start_time
+                log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
+
+            event.listen(engine, "before_cursor_execute",
+                         before_cursor_execute)
+            event.listen(engine, "after_cursor_execute",
+                         after_cursor_execute)
+
+    return engine
+
+
+def age(curdate):
+    """
+    turns a datetime into an age string.
+
+    :param curdate: datetime object
+    :rtype: unicode
+    :returns: unicode words describing age
+    """
+
+    from datetime import datetime
+    from webhelpers.date import time_ago_in_words
+
+    _ = lambda s: s
+
+    if not curdate:
+        return ''
+
+    agescales = [(_(u"year"), 3600 * 24 * 365),
+                 (_(u"month"), 3600 * 24 * 30),
+                 (_(u"day"), 3600 * 24),
+                 (_(u"hour"), 3600),
+                 (_(u"minute"), 60),
+                 (_(u"second"), 1), ]
+
+    age = datetime.now() - curdate
+    age_seconds = (age.days * agescales[2][1]) + age.seconds
+    pos = 1
+    for scale in agescales:
+        if scale[1] <= age_seconds:
+            if pos == 6:
+                pos = 5
+            return '%s %s' % (time_ago_in_words(curdate,
+                                                agescales[pos][0]), _('ago'))
+        pos += 1
+
+    return _(u'just now')
+
+
+def uri_filter(uri):
+    """
+    Removes user:password from given url string
+
+    :param uri:
+    :rtype: unicode
+    :returns: filtered list of strings
+    """
+    if not uri:
+        return ''
+
+    proto = ''
+
+    for pat in ('https://', 'http://'):
+        if uri.startswith(pat):
+            uri = uri[len(pat):]
+            proto = pat
+            break
+
+    # remove passwords and username
+    uri = uri[uri.find('@') + 1:]
+
+    # get the port
+    cred_pos = uri.find(':')
+    if cred_pos == -1:
+        host, port = uri, None
+    else:
+        host, port = uri[:cred_pos], uri[cred_pos + 1:]
+
+    return filter(None, [proto, host, port])
+
+
+def credentials_filter(uri):
+    """
+    Returns a url with removed credentials
+
+    :param uri:
+    """
+
+    uri = uri_filter(uri)
+    #check if we have port
+    if len(uri) > 2 and uri[2]:
+        uri[2] = ':' + uri[2]
+
+    return ''.join(uri)
+
+
+def get_changeset_safe(repo, rev):
+    """
+    Safe version of get_changeset if this changeset doesn't exists for a
+    repo it returns a Dummy one instead
+
+    :param repo:
+    :param rev:
+    """
+    from rhodecode.lib.vcs.backends.base import BaseRepository
+    from rhodecode.lib.vcs.exceptions import RepositoryError
+    if not isinstance(repo, BaseRepository):
+        raise Exception('You must pass an Repository '
+                        'object as first argument got %s', type(repo))
+
+    try:
+        cs = repo.get_changeset(rev)
+    except RepositoryError:
+        from rhodecode.lib.utils import EmptyChangeset
+        cs = EmptyChangeset(requested_revision=rev)
+    return cs
+
+
+def extract_mentioned_users(s):
+    """
+    Returns unique usernames from given string s that have @mention
+
+    :param s: string to get mentions
+    """
+    usrs = {}
+    for username in re.findall(r'(?:^@|\s@)(\w+)', s):
+        usrs[username] = username
+
+    return sorted(usrs.keys())
--- a/rhodecode/lib/vcs/backends/git/changeset.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/lib/vcs/backends/git/changeset.py	Wed Mar 28 19:54:16 2012 +0200
@@ -68,19 +68,24 @@
     def branch(self):
         # TODO: Cache as we walk (id <-> branch name mapping)
         refs = self.repository._repo.get_refs()
-        heads = [(key[len('refs/heads/'):], val) for key, val in refs.items()
-            if key.startswith('refs/heads/')]
+        heads = {}
+        for key, val in refs.items():
+            for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
+                if key.startswith(ref_key):
+                    n = key[len(ref_key):]
+                    if n not in ['HEAD']:
+                        heads[n] = val
 
-        for name, id in heads:
+        for name, id in heads.iteritems():
             walker = self.repository._repo.object_store.get_graph_walker([id])
             while True:
-                id = walker.next()
-                if not id:
+                id_ = walker.next()
+                if not id_:
                     break
-                if id == self.id:
+                if id_ == self.id:
                     return safe_unicode(name)
         raise ChangesetError("This should not happen... Have you manually "
-            "change id of the changeset?")
+                             "change id of the changeset?")
 
     def _fix_path(self, path):
         """
@@ -92,6 +97,7 @@
         return path
 
     def _get_id_for_path(self, path):
+
         # FIXME: Please, spare a couple of minutes and make those codes cleaner;
         if not path in self._paths:
             path = path.strip('/')
@@ -103,24 +109,23 @@
             splitted = path.split('/')
             dirs, name = splitted[:-1], splitted[-1]
             curdir = ''
+
+            # initially extract things from root dir
+            for item, stat, id in tree.iteritems():
+                if curdir:
+                    name = '/'.join((curdir, item))
+                else:
+                    name = item
+                self._paths[name] = id
+                self._stat_modes[name] = stat
+
             for dir in dirs:
                 if curdir:
                     curdir = '/'.join((curdir, dir))
                 else:
                     curdir = dir
-                #if curdir in self._paths:
-                    ## This path have been already traversed
-                    ## Update tree and continue
-                    #tree = self.repository._repo[self._paths[curdir]]
-                    #continue
                 dir_id = None
                 for item, stat, id in tree.iteritems():
-                    if curdir:
-                        item_path = '/'.join((curdir, item))
-                    else:
-                        item_path = item
-                    self._paths[item_path] = id
-                    self._stat_modes[item_path] = stat
                     if dir == item:
                         dir_id = id
                 if dir_id:
@@ -130,13 +135,16 @@
                         raise ChangesetError('%s is not a directory' % curdir)
                 else:
                     raise ChangesetError('%s have not been found' % curdir)
-            for item, stat, id in tree.iteritems():
-                if curdir:
-                    name = '/'.join((curdir, item))
-                else:
-                    name = item
-                self._paths[name] = id
-                self._stat_modes[name] = stat
+
+                # cache all items from the given traversed tree
+                for item, stat, id in tree.iteritems():
+                    if curdir:
+                        name = '/'.join((curdir, item))
+                    else:
+                        name = item
+                    self._paths[name] = id
+                    self._stat_modes[name] = stat
+
             if not path in self._paths:
                 raise NodeDoesNotExistError("There is no file nor directory "
                     "at the given path %r at revision %r"
--- a/rhodecode/model/__init__.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/model/__init__.py	Wed Mar 28 19:54:16 2012 +0200
@@ -85,14 +85,14 @@
 
         if isinstance(instance, cls):
             return instance
-        elif isinstance(instance, int) or str(instance).isdigit():
+        elif isinstance(instance, (int, long)) or str(instance).isdigit():
             return cls.get(instance)
         else:
             if instance:
                 if callback is None:
                     raise Exception(
-                        'given object must be int or Instance of %s got %s, '
-                        'no callback provided' % (cls, type(instance))
+                        'given object must be int, long or Instance of %s '
+                        'got %s, no callback provided' % (cls, type(instance))
                     )
                 else:
                     return callback(instance)
--- a/rhodecode/model/comment.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/model/comment.py	Wed Mar 28 19:54:16 2012 +0200
@@ -29,7 +29,7 @@
 from pylons.i18n.translation import _
 from sqlalchemy.util.compat import defaultdict
 
-from rhodecode.lib import extract_mentioned_users
+from rhodecode.lib.utils2 import extract_mentioned_users
 from rhodecode.lib import helpers as h
 from rhodecode.model import BaseModel
 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
@@ -63,6 +63,7 @@
         :param f_path:
         :param line_no:
         """
+
         if text:
             repo = Repository.get(repo_id)
             cs = repo.scm_instance.get_changeset(revision)
@@ -78,7 +79,6 @@
 
             self.sa.add(comment)
             self.sa.flush()
-
             # make notification
             line = ''
             if line_no:
--- a/rhodecode/model/db.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/model/db.py	Wed Mar 28 19:54:16 2012 +0200
@@ -39,7 +39,8 @@
 from rhodecode.lib.vcs.exceptions import VCSError
 from rhodecode.lib.vcs.utils.lazy import LazyProperty
 
-from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
+from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
+    safe_unicode
 from rhodecode.lib.compat import json
 from rhodecode.lib.caching_query import FromCache
 
@@ -145,12 +146,18 @@
         obj = cls.query().get(id_)
         Session.delete(obj)
 
+    def __repr__(self):
+        if hasattr(self, '__unicode__'):
+            # python repr needs to return str
+            return safe_str(self.__unicode__())
+        return '<DB:%s>' % (self.__class__.__name__)
 
 class RhodeCodeSetting(Base, BaseModel):
     __tablename__ = 'rhodecode_settings'
     __table_args__ = (
         UniqueConstraint('app_settings_name'),
-        {'extend_existing': True}
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
     )
     app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -181,8 +188,8 @@
         """
         self._app_settings_value = safe_unicode(val)
 
-    def __repr__(self):
-        return "<%s('%s:%s')>" % (
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
             self.__class__.__name__,
             self.app_settings_name, self.app_settings_value
         )
@@ -224,7 +231,8 @@
     __tablename__ = 'rhodecode_ui'
     __table_args__ = (
         UniqueConstraint('ui_key'),
-        {'extend_existing': True}
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
     )
 
     HOOK_UPDATE = 'changegroup.update'
@@ -274,7 +282,8 @@
     __tablename__ = 'users'
     __table_args__ = (
         UniqueConstraint('username'), UniqueConstraint('email'),
-        {'extend_existing': True}
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
     )
     user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -294,10 +303,15 @@
     repositories = relationship('Repository')
     user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
     repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
 
     group_member = relationship('UsersGroupMember', cascade='all')
 
-    notifications = relationship('UserNotification',)
+    notifications = relationship('UserNotification', cascade='all')
+    # notifications assigned to this user
+    user_created_notifications = relationship('Notification', cascade='all')
+    # comments created by this user
+    user_comments = relationship('ChangesetComment', cascade='all')
 
     @hybrid_property
     def email(self):
@@ -328,8 +342,8 @@
     def is_admin(self):
         return self.admin
 
-    def __repr__(self):
-        return "<%s('id:%s:%s')>" % (self.__class__.__name__,
+    def __unicode__(self):
+        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
                                      self.user_id, self.username)
 
     @classmethod
@@ -376,6 +390,9 @@
 
     def __json__(self):
         return dict(
+            user_id=self.user_id,
+            first_name=self.name,
+            last_name=self.lastname,
             email=self.email,
             full_name=self.full_name,
             full_name_or_username=self.full_name_or_username,
@@ -386,7 +403,10 @@
 
 class UserLog(Base, BaseModel):
     __tablename__ = 'user_logs'
-    __table_args__ = {'extend_existing': True}
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'},
+    )
     user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
     repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
@@ -405,7 +425,10 @@
 
 class UsersGroup(Base, BaseModel):
     __tablename__ = 'users_groups'
-    __table_args__ = {'extend_existing': True}
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'},
+    )
 
     users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
@@ -413,9 +436,10 @@
 
     members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
     users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
+    users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
 
-    def __repr__(self):
-        return '<userGroup(%s)>' % (self.users_group_name)
+    def __unicode__(self):
+        return u'<userGroup(%s)>' % (self.users_group_name)
 
     @classmethod
     def get_by_group_name(cls, group_name, cache=False,
@@ -443,7 +467,10 @@
 
 class UsersGroupMember(Base, BaseModel):
     __tablename__ = 'users_groups_members'
-    __table_args__ = {'extend_existing': True}
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'},
+    )
 
     users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
@@ -461,7 +488,8 @@
     __tablename__ = 'repositories'
     __table_args__ = (
         UniqueConstraint('repo_name'),
-        {'extend_existing': True},
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'},
     )
 
     repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -489,9 +517,9 @@
 
     logs = relationship('UserLog')
 
-    def __repr__(self):
-        return "<%s('%s:%s')>" % (self.__class__.__name__,
-                                  self.repo_id, self.repo_name)
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
+                                   self.repo_name)
 
     @classmethod
     def url_sep(cls):
@@ -710,7 +738,8 @@
     __table_args__ = (
         UniqueConstraint('group_name', 'group_parent_id'),
         CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True},
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'},
     )
     __mapper_args__ = {'order_by': 'group_name'}
 
@@ -728,8 +757,8 @@
         self.group_name = group_name
         self.parent_group = parent_group
 
-    def __repr__(self):
-        return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
                                   self.group_name)
 
     @classmethod
@@ -837,13 +866,16 @@
 
 class Permission(Base, BaseModel):
     __tablename__ = 'permissions'
-    __table_args__ = {'extend_existing': True}
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'},
+    )
     permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
     permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
-    def __repr__(self):
-        return "<%s('%s:%s')>" % (
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
             self.__class__.__name__, self.permission_id, self.permission_name
         )
 
@@ -874,7 +906,8 @@
     __tablename__ = 'repo_to_perm'
     __table_args__ = (
         UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'extend_existing': True}
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
     )
     repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
@@ -894,15 +927,16 @@
         Session.add(n)
         return n
 
-    def __repr__(self):
-        return '<user:%s => %s >' % (self.user, self.repository)
+    def __unicode__(self):
+        return u'<user:%s => %s >' % (self.user, self.repository)
 
 
 class UserToPerm(Base, BaseModel):
     __tablename__ = 'user_to_perm'
     __table_args__ = (
         UniqueConstraint('user_id', 'permission_id'),
-        {'extend_existing': True}
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
     )
     user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
@@ -916,7 +950,8 @@
     __tablename__ = 'users_group_repo_to_perm'
     __table_args__ = (
         UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'extend_existing': True}
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
     )
     users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
@@ -936,15 +971,16 @@
         Session.add(n)
         return n
 
-    def __repr__(self):
-        return '<userGroup:%s => %s >' % (self.users_group, self.repository)
+    def __unicode__(self):
+        return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
 
 
 class UsersGroupToPerm(Base, BaseModel):
     __tablename__ = 'users_group_to_perm'
     __table_args__ = (
         UniqueConstraint('users_group_id', 'permission_id',),
-        {'extend_existing': True}
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
     )
     users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
@@ -958,7 +994,8 @@
     __tablename__ = 'user_repo_group_to_perm'
     __table_args__ = (
         UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True}
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
     )
 
     group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -975,7 +1012,8 @@
     __tablename__ = 'users_group_repo_group_to_perm'
     __table_args__ = (
         UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True}
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
     )
 
     users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -990,7 +1028,11 @@
 
 class Statistics(Base, BaseModel):
     __tablename__ = 'statistics'
-    __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
+    __table_args__ = (
+         UniqueConstraint('repository_id'),
+         {'extend_existing': True, 'mysql_engine':'InnoDB',
+          'mysql_charset': 'utf8'}
+    )
     stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
     stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
@@ -1006,7 +1048,8 @@
     __table_args__ = (
         UniqueConstraint('user_id', 'follows_repository_id'),
         UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True}
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
     )
 
     user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -1027,7 +1070,11 @@
 
 class CacheInvalidation(Base, BaseModel):
     __tablename__ = 'cache_invalidation'
-    __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
+    __table_args__ = (
+        UniqueConstraint('cache_key'),
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'},
+    )
     cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
     cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -1038,14 +1085,17 @@
         self.cache_args = cache_args
         self.cache_active = False
 
-    def __repr__(self):
-        return "<%s('%s:%s')>" % (self.__class__.__name__,
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (self.__class__.__name__,
                                   self.cache_id, self.cache_key)
+    @classmethod
+    def clear_cache(cls):
+        cls.query().delete()
 
     @classmethod
     def _get_key(cls, key):
         """
-        Wrapper for generating a key
+        Wrapper for generating a key, together with a prefix
 
         :param key:
         """
@@ -1054,13 +1104,26 @@
         iid = rhodecode.CONFIG.get('instance_id')
         if iid:
             prefix = iid
-        return "%s%s" % (prefix, key)
+        return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
 
     @classmethod
     def get_by_key(cls, key):
         return cls.query().filter(cls.cache_key == key).scalar()
 
     @classmethod
+    def _get_or_create_key(cls, key, prefix, org_key):
+        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()
+            except Exception:
+                log.error(traceback.format_exc())
+                Session.rollback()
+        return inv_obj
+
+    @classmethod
     def invalidate(cls, key):
         """
         Returns Invalidation object if this given key should be invalidated
@@ -1069,10 +1132,12 @@
 
         :param key:
         """
-        return cls.query()\
-                .filter(CacheInvalidation.cache_key == key)\
-                .filter(CacheInvalidation.cache_active == False)\
-                .scalar()
+
+        key, _prefix, _org_key = cls._get_key(key)
+        inv = cls._get_or_create_key(key, _prefix, _org_key)
+
+        if inv and inv.cache_active is False:
+            return inv
 
     @classmethod
     def set_invalidate(cls, key):
@@ -1082,17 +1147,16 @@
         :param key:
         """
 
-        log.debug('marking %s for invalidation' % key)
-        inv_obj = Session.query(cls)\
-            .filter(cls.cache_key == key).scalar()
-        if inv_obj:
-            inv_obj.cache_active = False
-        else:
-            log.debug('cache key not found in invalidation db -> creating one')
-            inv_obj = CacheInvalidation(key)
+        key, _prefix, _org_key = cls._get_key(key)
+        inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
+        log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
+                                                             _org_key))
+        try:
+            for inv_obj in inv_objs:
+                if inv_obj:
+                    inv_obj.cache_active = False
 
-        try:
-            Session.add(inv_obj)
+                Session.add(inv_obj)
             Session.commit()
         except Exception:
             log.error(traceback.format_exc())
@@ -1113,7 +1177,10 @@
 
 class ChangesetComment(Base, BaseModel):
     __tablename__ = 'changeset_comments'
-    __table_args__ = ({'extend_existing': True},)
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'},
+    )
     comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
     repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
     revision = Column('revision', String(40), nullable=False)
@@ -1142,7 +1209,10 @@
 
 class Notification(Base, BaseModel):
     __tablename__ = 'notifications'
-    __table_args__ = ({'extend_existing': True},)
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'},
+    )
 
     TYPE_CHANGESET_COMMENT = u'cs_comment'
     TYPE_MESSAGE = u'message'
@@ -1194,7 +1264,8 @@
     __tablename__ = 'user_to_notification'
     __table_args__ = (
         UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True}
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
     )
     user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
     notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
@@ -1212,7 +1283,10 @@
 
 class DbMigrateVersion(Base, BaseModel):
     __tablename__ = 'db_migrate_version'
-    __table_args__ = {'extend_existing': True}
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'},
+    )
     repository_id = Column('repository_id', String(250), primary_key=True)
     repository_path = Column('repository_path', Text)
     version = Column('version', Integer)
--- a/rhodecode/model/notification.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/model/notification.py	Wed Mar 28 19:54:16 2012 +0200
@@ -32,6 +32,7 @@
 from pylons.i18n.translation import _
 
 import rhodecode
+from rhodecode.config.conf import DATETIME_FORMAT
 from rhodecode.lib import helpers as h
 from rhodecode.model import BaseModel
 from rhodecode.model.db import Notification, User, UserNotification
@@ -47,11 +48,11 @@
     def __get_notification(self, notification):
         if isinstance(notification, Notification):
             return notification
-        elif isinstance(notification, int):
+        elif isinstance(notification, (int, long)):
             return Notification.get(notification)
         else:
             if notification:
-                raise Exception('notification must be int or Instance'
+                raise Exception('notification must be int, long or Instance'
                                 ' of Notification got %s' % type(notification))
 
     def create(self, created_by, subject, body, recipients=None,
@@ -111,6 +112,7 @@
             kwargs.update(email_kwargs)
             email_body_html = EmailNotificationModel()\
                                 .get_email_tmpl(type_, **kwargs)
+
             run_task(tasks.send_email, rec.email, email_subject, email_body,
                      email_body_html)
 
@@ -176,14 +178,13 @@
             notification.TYPE_REGISTRATION: _('registered in RhodeCode')
         }
 
-        DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
-
         tmpl = "%(user)s %(action)s %(when)s"
         if show_age:
             when = h.age(notification.created_on)
         else:
             DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
             when = DTF(notification.created_on)
+
         data = dict(
             user=notification.created_by_user.username,
             action=_map[notification.type_], when=when,
--- a/rhodecode/model/repo.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/model/repo.py	Wed Mar 28 19:54:16 2012 +0200
@@ -29,15 +29,15 @@
 from datetime import datetime
 
 from rhodecode.lib.vcs.backends import get_backend
-
-from rhodecode.lib import LazyProperty
-from rhodecode.lib import safe_str, safe_unicode
+from rhodecode.lib.compat import json
+from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode
 from rhodecode.lib.caching_query import FromCache
 from rhodecode.lib.hooks import log_create_repository
 
 from rhodecode.model import BaseModel
 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
     Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
+from rhodecode.lib import helpers as h
 
 
 log = logging.getLogger(__name__)
@@ -95,25 +95,28 @@
         return repo.scalar()
 
     def get_users_js(self):
-
         users = self.sa.query(User).filter(User.active == True).all()
-        u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
-        users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
-                                                    u.lastname, u.username)
-                                        for u in users])
-        return users_array
+        return json.dumps([
+            {
+             'id': u.user_id,
+             'fname': u.name,
+             'lname': u.lastname,
+             'nname': u.username,
+             'gravatar_lnk': h.gravatar_url(u.email, 14)
+            } for u in users]
+        )
 
     def get_users_groups_js(self):
         users_groups = self.sa.query(UsersGroup)\
             .filter(UsersGroup.users_group_active == True).all()
 
-        g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
-
-        users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
-                                    (gr.users_group_id, gr.users_group_name,
-                                     len(gr.members))
-                                        for gr in users_groups])
-        return users_groups_array
+        return json.dumps([
+            {
+             'id': gr.users_group_id,
+             'grname': gr.users_group_name,
+             'grmembers': len(gr.members),
+            } for gr in users_groups]
+        )
 
     def _get_defaults(self, repo_name):
         """
@@ -346,6 +349,7 @@
         :param repo: Instance of Repository, repository_id, or repository name
         :param user: Instance of User, user_id or username
         """
+
         user = self.__get_user(user)
         repo = self.__get_repo(repo)
 
--- a/rhodecode/model/repos_group.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/model/repos_group.py	Wed Mar 28 19:54:16 2012 +0200
@@ -28,7 +28,7 @@
 import traceback
 import shutil
 
-from rhodecode.lib import LazyProperty
+from rhodecode.lib.utils2 import LazyProperty
 
 from rhodecode.model import BaseModel
 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
--- a/rhodecode/model/scm.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/model/scm.py	Wed Mar 28 19:54:16 2012 +0200
@@ -35,7 +35,7 @@
 
 from rhodecode import BACKENDS
 from rhodecode.lib import helpers as h
-from rhodecode.lib import safe_str
+from rhodecode.lib.utils2 import safe_str
 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
     action_logger, EmptyChangeset, REMOVED_REPO_PAT
@@ -235,13 +235,13 @@
         return group_iter
 
     def mark_for_invalidation(self, repo_name):
-        """Puts cache invalidation task into db for
+        """
+        Puts cache invalidation task into db for
         further global cache invalidation
 
         :param repo_name: this repo that should invalidation take place
         """
         CacheInvalidation.set_invalidate(repo_name)
-        CacheInvalidation.set_invalidate(repo_name + "_README")
 
     def toggle_following_repo(self, follow_repo_id, user_id):
 
--- a/rhodecode/model/user.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/model/user.py	Wed Mar 28 19:54:16 2012 +0200
@@ -29,18 +29,19 @@
 from pylons import url
 from pylons.i18n.translation import _
 
-from rhodecode.lib import safe_unicode
+from rhodecode.lib.utils2 import safe_unicode, generate_api_key
 from rhodecode.lib.caching_query import FromCache
 
 from rhodecode.model import BaseModel
 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
     UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
-    Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup
+    Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup,\
+    UsersGroupRepoGroupToPerm
 from rhodecode.lib.exceptions import DefaultUserException, \
     UserOwnsReposException
 
 from sqlalchemy.exc import DatabaseError
-from rhodecode.lib import generate_api_key
+
 from sqlalchemy.orm import joinedload
 
 log = logging.getLogger(__name__)
@@ -298,14 +299,16 @@
         try:
             if user.username == 'default':
                 raise DefaultUserException(
-                                _("You can't remove this user since it's"
-                                  " crucial for entire application"))
+                    _(u"You can't remove this user since it's"
+                      " crucial for entire application")
+                )
             if user.repositories:
-                raise UserOwnsReposException(_('This user still owns %s '
-                                               'repositories and cannot be '
-                                               'removed. Switch owners or '
-                                               'remove those repositories') \
-                                               % user.repositories)
+                repos = [x.repo_name for x in user.repositories]
+                raise UserOwnsReposException(
+                    _(u'user "%s" still owns %s repositories and cannot be '
+                      'removed. Switch owners or remove those repositories. %s')
+                    % (user.username, len(repos), ', '.join(repos))
+                )
             self.sa.delete(user)
         except:
             log.error(traceback.format_exc())
@@ -409,7 +412,7 @@
             for perm in default_global_perms:
                 user.permissions[GLOBAL].add(perm.permission.permission_name)
 
-            # default for repositories
+            # defaults for repositories, taken from default user
             for perm in default_repo_perms:
                 r_k = perm.UserRepoToPerm.repository.repo_name
                 if perm.Repository.private and not (perm.Repository.user_id == uid):
@@ -423,17 +426,18 @@
 
                 user.permissions[RK][r_k] = p
 
-            # default for repositories groups
+            # defaults for repositories groups taken from default user permission
+            # on given group
             for perm in default_repo_groups_perms:
                 rg_k = perm.UserRepoGroupToPerm.group.group_name
                 p = perm.Permission.permission_name
                 user.permissions[GK][rg_k] = p
 
             #==================================================================
-            # overwrite default with user permissions if any
+            # overwrite defaults with user permissions if any found
             #==================================================================
 
-            # user global
+            # user global permissions
             user_perms = self.sa.query(UserToPerm)\
                     .options(joinedload(UserToPerm.permission))\
                     .filter(UserToPerm.user_id == uid).all()
@@ -441,7 +445,7 @@
             for perm in user_perms:
                 user.permissions[GLOBAL].add(perm.permission.permission_name)
 
-            # user repositories
+            # user explicit permissions for repositories
             user_repo_perms = \
              self.sa.query(UserRepoToPerm, Permission, Repository)\
              .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
@@ -459,8 +463,8 @@
                 user.permissions[RK][r_k] = p
 
             #==================================================================
-            # check if user is part of groups for this repository and fill in
-            # (or replace with higher) permissions
+            # check if user is part of user groups for this repository and
+            # fill in (or replace with higher) permissions
             #==================================================================
 
             # users group global
@@ -473,7 +477,7 @@
             for perm in user_perms_from_users_groups:
                 user.permissions[GLOBAL].add(perm.permission.permission_name)
 
-            # users group repositories
+            # users group for repositories permissions
             user_repo_perms_from_users_groups = \
              self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
              .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
@@ -495,12 +499,12 @@
             # get access for this user for repos group and override defaults
             #==================================================================
 
-            # user repositories groups
+            # user explicit permissions for repository
             user_repo_groups_perms = \
              self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
              .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
              .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
-             .filter(UserRepoToPerm.user_id == uid)\
+             .filter(UserRepoGroupToPerm.user_id == uid)\
              .all()
 
             for perm in user_repo_groups_perms:
@@ -510,6 +514,30 @@
                 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
                     user.permissions[GK][rg_k] = p
 
+            #==================================================================
+            # check if user is part of user groups for this repo group and
+            # fill in (or replace with higher) permissions
+            #==================================================================
+
+            # users group for repositories permissions
+            user_repo_group_perms_from_users_groups = \
+             self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
+             .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
+             .join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\
+             .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
+             .filter(UsersGroupMember.user_id == uid)\
+             .all()
+
+            for perm in user_repo_group_perms_from_users_groups:
+                g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
+                print perm, g_k
+                p = perm.Permission.permission_name
+                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]:
+                    user.permissions[GK][g_k] = p
+
         return user
 
     def has_perm(self, user, perm):
--- a/rhodecode/public/css/style.css	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/public/css/style.css	Wed Mar 28 19:54:16 2012 +0200
@@ -185,6 +185,41 @@
 	border-bottom-right-radius: 8px;
 }
 
+.top-left-rounded-corner-mid {
+    -webkit-border-top-left-radius: 4px;
+    -khtml-border-radius-topleft: 4px;
+    -moz-border-radius-topleft: 4px;
+    border-top-left-radius: 4px;
+}
+
+.top-right-rounded-corner-mid {
+    -webkit-border-top-right-radius: 4px;
+    -khtml-border-radius-topright: 4px;
+    -moz-border-radius-topright: 4px;
+    border-top-right-radius: 4px;
+}
+
+.bottom-left-rounded-corner-mid {
+    -webkit-border-bottom-left-radius: 4px;
+    -khtml-border-radius-bottomleft: 4px;
+    -moz-border-radius-bottomleft: 4px;
+    border-bottom-left-radius: 4px;
+}
+
+.bottom-right-rounded-corner-mid {
+    -webkit-border-bottom-right-radius: 4px;
+    -khtml-border-radius-bottomright: 4px;
+    -moz-border-radius-bottomright: 4px;
+    border-bottom-right-radius: 4px;
+}
+
+.help-block {
+    color: #999999;
+    display: block;
+    margin-bottom: 0;
+    margin-top: 5px;
+}
+
 #header {
 	margin: 0;
 	padding: 0 10px;
@@ -197,18 +232,16 @@
 	-moz-border-radius: 0px 0px 8px 8px;
 	border-radius: 0px 0px 8px 8px;
 	height: 37px;
-    background-color: #eedc94;
+    background-color: #003B76;
     background-repeat: repeat-x;
-    background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
-        to(#eedc94) );
+    background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
     background-image: -moz-linear-gradient(top, #003b76, #00376e);
     background-image: -ms-linear-gradient(top, #003b76, #00376e);
     background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
     background-image: -webkit-linear-gradient(top, #003b76, #00376e);
     background-image: -o-linear-gradient(top, #003b76, #00376e);
     background-image: linear-gradient(top, #003b76, #00376e);
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
-        endColorstr='#00376e', GradientType=0 );
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
 	box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 }
 
@@ -260,9 +293,9 @@
 	min-height: 44px;
 	clear: both;
 	position: relative;
-    background-color: #eedc94;
+    background-color: #003B76;
     background-repeat: repeat-x;
-    background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),to(#eedc94) );
+    background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
     background-image: -moz-linear-gradient(top, #003b76, #00376e);
     background-image: -ms-linear-gradient(top, #003b76, #00376e);
     background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),color-stop(100%, #00376e) );
@@ -289,6 +322,14 @@
     -moz-border-radius: 0px 0px 0px 0px;
     border-radius: 0px 0px 0px 0px;	
 }
+
+.ie7 #header #header-inner.hover,
+.ie8 #header #header-inner.hover,
+.ie9 #header #header-inner.hover
+{
+    z-index: auto !important;
+}
+
 #header #header-inner #home a {
 	height: 40px;
 	width: 46px;
@@ -997,9 +1038,9 @@
 #content div.box div.title {
 	clear: both;
 	overflow: hidden;
-	background-color: #eedc94;
+	background-color: #003B76;
 	background-repeat: repeat-x;
-	background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
+	background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
 	background-image: -moz-linear-gradient(top, #003b76, #00376e);
 	background-image: -ms-linear-gradient(top, #003b76, #00376e);
 	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
@@ -1758,33 +1799,21 @@
 }
 
 #footer div#footer-inner {
-	background-color: #eedc94; background-repeat : repeat-x;
-	background-image : -khtml-gradient( linear, left top, left bottom,
-	from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient(
-	top, #003b76, #00376e); background-image : -ms-linear-gradient( top,
-	#003b76, #00376e); background-image : -webkit-gradient( linear, left
-	top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
+	background-color: #003B76; 
+	background-repeat : repeat-x;
+	background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E)); 
+	background-image : -moz-linear-gradient(top, #003b76, #00376e); 
+	background-image : -ms-linear-gradient( top, #003b76, #00376e); 
+	background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
 	background-image : -webkit-linear-gradient( top, #003b76, #00376e));
 	background-image : -o-linear-gradient( top, #003b76, #00376e));
-	background-image : linear-gradient( top, #003b76, #00376e); filter :
-	progid : DXImageTransform.Microsoft.gradient ( startColorstr =
-	'#003b76', endColorstr = '#00376e', GradientType = 0);
+	background-image : linear-gradient( top, #003b76, #00376e); 
+	filter :progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
 	box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 	-webkit-border-radius: 4px 4px 4px 4px;
 	-khtml-border-radius: 4px 4px 4px 4px;
 	-moz-border-radius: 4px 4px 4px 4px;
 	border-radius: 4px 4px 4px 4px;
-	background-repeat: repeat-x;
-	background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
-		to(#eedc94) );
-	background-image: -moz-linear-gradient(top, #003b76, #00376e);
-	background-image: -ms-linear-gradient(top, #003b76, #00376e);
-	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
-	background-image: -webkit-linear-gradient(top, #003b76, #00376e);
-	background-image: -o-linear-gradient(top, #003b76, #00376e);
-	background-image: linear-gradient(top, #003b76, #00376e);
-	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
-		endColorstr='#00376e', GradientType=0 );
 }
 
 #footer div#footer-inner p {
@@ -1808,30 +1837,18 @@
 	clear: both;
 	overflow: hidden;
 	position: relative;
-	background-color: #eedc94; background-repeat : repeat-x;
-	background-image : -khtml-gradient( linear, left top, left bottom,
-	from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient(
-	top, #003b76, #00376e); background-image : -ms-linear-gradient( top,
-	#003b76, #00376e); background-image : -webkit-gradient( linear, left
-	top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
+	background-color: #003B76; 
+	background-repeat : repeat-x;
+	background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E)); 
+	background-image : -moz-linear-gradient( top, #003b76, #00376e); 
+	background-image : -ms-linear-gradient( top, #003b76, #00376e);
+	background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
 	background-image : -webkit-linear-gradient( top, #003b76, #00376e));
 	background-image : -o-linear-gradient( top, #003b76, #00376e));
-	background-image : linear-gradient( top, #003b76, #00376e); filter :
-	progid : DXImageTransform.Microsoft.gradient ( startColorstr =
-	'#003b76', endColorstr = '#00376e', GradientType = 0);
+	background-image : linear-gradient( top, #003b76, #00376e); 
+	filter : progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
 	margin: 0 auto;
 	padding: 0;
-	background-repeat: repeat-x;
-	background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
-		to(#eedc94) );
-	background-image: -moz-linear-gradient(top, #003b76, #00376e);
-	background-image: -ms-linear-gradient(top, #003b76, #00376e);
-	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
-	background-image: -webkit-linear-gradient(top, #003b76, #00376e);
-	background-image: -o-linear-gradient(top, #003b76, #00376e);
-	background-image: linear-gradient(top, #003b76, #00376e);
-	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
-		endColorstr='#00376e', GradientType=0 );
 }
 
 #login div.inner {
@@ -1908,16 +1925,14 @@
     width: 278px;
     
     background-repeat: repeat-x;
-    background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
-        to(#eedc94) );
+    background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
     background-image: -moz-linear-gradient(top, #003b76, #00376e);
     background-image: -ms-linear-gradient(top, #003b76, #00376e);
     background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
     background-image: -webkit-linear-gradient(top, #003b76, #00376e);
     background-image: -o-linear-gradient(top, #003b76, #00376e);
     background-image: linear-gradient(top, #003b76, #00376e);
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
-        endColorstr='#00376e', GradientType=0 );
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
 
 	z-index: 999;
 	-webkit-border-radius: 0px 0px 4px 4px;
@@ -2060,10 +2075,9 @@
 	clear: both;
 	overflow: hidden;
 	position: relative;
-    background-color: #eedc94;
+    background-color: #003B76;
     background-repeat: repeat-x;
-    background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
-        to(#eedc94) );
+    background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
     background-image: -moz-linear-gradient(top, #003b76, #00376e);
     background-image: -ms-linear-gradient(top, #003b76, #00376e);
     background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
@@ -2794,12 +2808,12 @@
 }
 
 .ac .yui-ac {
-	position: relative;
+	position: inherit;
 	font-size: 100%;
 }
 
 .ac .perm_ac {
-	width: 15em;
+	width: 20em;
 }
 
 .ac .yui-ac-input {
@@ -2809,16 +2823,15 @@
 .ac .yui-ac-container {
 	position: absolute;
 	top: 1.6em;
-	width: 100%;
+	width: auto;
 }
 
 .ac .yui-ac-content {
 	position: absolute;
-	width: 100%;
 	border: 1px solid gray;
 	background: #fff;
-	overflow: hidden;
 	z-index: 9050;
+	
 }
 
 .ac .yui-ac-shadow {
@@ -2827,7 +2840,7 @@
 	background: #000;
 	-moz-opacity: 0.1px;
 	opacity: .10;
-	filter: alpha(opacity =     10);
+	filter: alpha(opacity = 10);
 	z-index: 9049;
 	margin: .3em;
 }
@@ -2836,6 +2849,7 @@
 	width: 100%;
 	margin: 0;
 	padding: 0;
+	z-index: 9050;
 }
 
 .ac .yui-ac-content li {
@@ -2843,15 +2857,28 @@
 	white-space: nowrap;
 	margin: 0;
 	padding: 2px 5px;
+	height: 18px;
+	z-index: 9050;
+	display: block;
+	width: auto !important;
+}
+
+.ac .yui-ac-content li .ac-container-wrap{
+    width: auto;
 }
 
 .ac .yui-ac-content li.yui-ac-prehighlight {
 	background: #B3D4FF;
+	z-index: 9050;
 }
 
 .ac .yui-ac-content li.yui-ac-highlight {
 	background: #556CB5;
 	color: #FFF;
+	z-index: 9050;
+}
+.ac .yui-ac-bd{
+	z-index: 9050;
 }
 
 .follow {
@@ -3006,17 +3033,14 @@
 .error_msg {
 	background-color: #c43c35;
 	background-repeat: repeat-x;
-	background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b),
-		to(#c43c35) );
+	background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35) );
 	background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
 	background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
-	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b),
-		color-stop(100%, #c43c35) );
+	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35) );
 	background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
 	background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
 	background-image: linear-gradient(top, #ee5f5b, #c43c35);
-	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',
-		endColorstr='#c43c35', GradientType=0 );
+	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35', GradientType=0 );
 	border-color: #c43c35 #c43c35 #882a25;
 }
 
@@ -3024,51 +3048,42 @@
 	color: #404040 !important;
 	background-color: #eedc94;
 	background-repeat: repeat-x;
-	background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
-		to(#eedc94) );
+	background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
 	background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
 	background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
-	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1),
-		color-stop(100%, #eedc94) );
+	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94) );
 	background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
 	background-image: -o-linear-gradient(top, #fceec1, #eedc94);
 	background-image: linear-gradient(top, #fceec1, #eedc94);
-	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1',
-		endColorstr='#eedc94', GradientType=0 );
+	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0 );
 	border-color: #eedc94 #eedc94 #e4c652;
 }
 
 .success_msg {
 	background-color: #57a957;
 	background-repeat: repeat-x !important;
-	background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462),
-		to(#57a957) );
+	background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957) );
 	background-image: -moz-linear-gradient(top, #62c462, #57a957);
 	background-image: -ms-linear-gradient(top, #62c462, #57a957);
-	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462),
-		color-stop(100%, #57a957) );
+	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957) );
 	background-image: -webkit-linear-gradient(top, #62c462, #57a957);
 	background-image: -o-linear-gradient(top, #62c462, #57a957);
 	background-image: linear-gradient(top, #62c462, #57a957);
-	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462',
-		endColorstr='#57a957', GradientType=0 );
+	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0 );
 	border-color: #57a957 #57a957 #3d773d;
 }
 
 .notice_msg {
 	background-color: #339bb9;
 	background-repeat: repeat-x;
-	background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de),
-		to(#339bb9) );
+	background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9) );
 	background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
 	background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
-	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de),
-		color-stop(100%, #339bb9) );
+	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9) );
 	background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
 	background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
 	background-image: linear-gradient(top, #5bc0de, #339bb9);
-	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de',
-		endColorstr='#339bb9', GradientType=0 );
+	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0 );
 	border-color: #339bb9 #339bb9 #22697d;
 }
 
@@ -3096,8 +3111,7 @@
 }
 
 #msg_close {
-	background: transparent url("../icons/cross_grey_small.png") no-repeat
-		scroll 0 0;
+	background: transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
 	cursor: pointer;
 	height: 16px;
 	position: absolute;
@@ -3105,7 +3119,12 @@
 	top: 5px;
 	width: 16px;
 }
-
+div#legend_data{
+	padding-left:10px;
+}
+div#legend_container table{
+	border: none !important;
+}
 div#legend_container table,div#legend_choices table {
 	width: auto !important;
 }
@@ -4115,6 +4134,56 @@
     padding:5px 0px 5px 38px;
 }
 
+/****
+  PERMS
+*****/
+#perms .perms_section_head {
+   padding:10px 10px 10px 0px;
+   font-size:16px;
+   font-weight: bold;
+}
+
+#perms .perm_tag{
+  padding: 1px 3px 1px 3px;
+  font-size: 10px;
+  font-weight: bold;
+  text-transform: uppercase;
+  white-space: nowrap;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+
+#perms .perm_tag.admin{
+  background-color: #B94A48;
+  color: #ffffff;
+}
+
+#perms .perm_tag.write{
+  background-color: #B94A48;
+  color: #ffffff;    
+}
+
+#perms .perm_tag.read{
+  background-color: #468847;
+  color: #ffffff;    
+}
+
+#perms .perm_tag.none{
+  background-color: #bfbfbf;
+  color: #ffffff;    
+}
+
+.perm-gravatar{
+	vertical-align:middle;
+	padding:2px;
+}
+.perm-gravatar-ac{
+    vertical-align:middle;
+    padding:2px;
+    width: 14px;
+    height: 14px;	
+}
 
 /*****************************************************************************
                                   DIFFS CSS
--- a/rhodecode/public/js/rhodecode.js	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/public/js/rhodecode.js	Wed Mar 28 19:54:16 2012 +0200
@@ -609,6 +609,178 @@
 };	
 
 
+/** MEMBERS AUTOCOMPLETE WIDGET **/
+
+var MembersAutoComplete = function (users_list, groups_list, group_lbl, members_lbl) {
+    var myUsers = users_list;
+    var myGroups = groups_list;
+
+    // Define a custom search function for the DataSource of users
+    var matchUsers = function (sQuery) {
+            // Case insensitive matching
+            var query = sQuery.toLowerCase();
+            var i = 0;
+            var l = myUsers.length;
+            var matches = [];
+
+            // Match against each name of each contact
+            for (; i < l; i++) {
+                contact = myUsers[i];
+                if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
+                    matches[matches.length] = contact;
+                }
+            }
+            return matches;
+        };
+
+    // Define a custom search function for the DataSource of usersGroups
+    var matchGroups = function (sQuery) {
+            // Case insensitive matching
+            var query = sQuery.toLowerCase();
+            var i = 0;
+            var l = myGroups.length;
+            var matches = [];
+
+            // Match against each name of each contact
+            for (; i < l; i++) {
+                matched_group = myGroups[i];
+                if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
+                    matches[matches.length] = matched_group;
+                }
+            }
+            return matches;
+        };
+
+    //match all
+    var matchAll = function (sQuery) {
+            u = matchUsers(sQuery);
+            g = matchGroups(sQuery);
+            return u.concat(g);
+        };
+
+    // DataScheme for members
+    var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
+    memberDS.responseSchema = {
+        fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
+    };
+
+    // DataScheme for owner
+    var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
+    ownerDS.responseSchema = {
+        fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
+    };
+
+    // Instantiate AutoComplete for perms
+    var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
+    membersAC.useShadow = false;
+    membersAC.resultTypeList = false;
+
+    // Instantiate AutoComplete for owner
+    var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
+    ownerAC.useShadow = false;
+    ownerAC.resultTypeList = false;
+
+
+    // Helper highlight function for the formatter
+    var highlightMatch = function (full, snippet, matchindex) {
+            return full.substring(0, matchindex) 
+            + "<span class='match'>" 
+            + full.substr(matchindex, snippet.length) 
+            + "</span>" + full.substring(matchindex + snippet.length);
+        };
+
+    // Custom formatter to highlight the matching letters
+    var custom_formatter = function (oResultData, sQuery, sResultMatch) {
+            var query = sQuery.toLowerCase();
+            var _gravatar = function(res, em, group){
+            	if (group !== undefined){
+            		em = '/images/icons/group.png'
+            	}
+            	tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
+            	return tmpl.format(em,res)
+            }
+            // group
+            if (oResultData.grname != undefined) {
+                var grname = oResultData.grname;
+                var grmembers = oResultData.grmembers;
+                var grnameMatchIndex = grname.toLowerCase().indexOf(query);
+                var grprefix = "{0}: ".format(group_lbl);
+                var grsuffix = " (" + grmembers + "  )";
+                var grsuffix = " ({0}  {1})".format(grmembers, members_lbl);
+
+                if (grnameMatchIndex > -1) {
+                    return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true);
+                }
+			    return _gravatar(grprefix + oResultData.grname + grsuffix, null,true);
+            // Users
+            } else if (oResultData.fname != undefined) {
+                var fname = oResultData.fname,
+                    lname = oResultData.lname,
+                    nname = oResultData.nname || "",
+                    // Guard against null value
+                    fnameMatchIndex = fname.toLowerCase().indexOf(query),
+                    lnameMatchIndex = lname.toLowerCase().indexOf(query),
+                    nnameMatchIndex = nname.toLowerCase().indexOf(query),
+                    displayfname, displaylname, displaynname;
+
+                if (fnameMatchIndex > -1) {
+                    displayfname = highlightMatch(fname, query, fnameMatchIndex);
+                } else {
+                    displayfname = fname;
+                }
+
+                if (lnameMatchIndex > -1) {
+                    displaylname = highlightMatch(lname, query, lnameMatchIndex);
+                } else {
+                    displaylname = lname;
+                }
+
+                if (nnameMatchIndex > -1) {
+                    displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
+                } else {
+                    displaynname = nname ? "(" + nname + ")" : "";
+                }
+
+                return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
+            } else {
+                return '';
+            }
+        };
+    membersAC.formatResult = custom_formatter;
+    ownerAC.formatResult = custom_formatter;
+
+    var myHandler = function (sType, aArgs) {
+
+            var myAC = aArgs[0]; // reference back to the AC instance
+            var elLI = aArgs[1]; // reference to the selected LI element
+            var oData = aArgs[2]; // object literal of selected item's result data
+            //fill the autocomplete with value
+            if (oData.nname != undefined) {
+                //users
+                myAC.getInputEl().value = oData.nname;
+                YUD.get('perm_new_member_type').value = 'user';
+            } else {
+                //groups
+                myAC.getInputEl().value = oData.grname;
+                YUD.get('perm_new_member_type').value = 'users_group';
+            }
+        };
+
+    membersAC.itemSelectEvent.subscribe(myHandler);
+    if(ownerAC.itemSelectEvent){
+    	ownerAC.itemSelectEvent.subscribe(myHandler);
+    }
+
+    return {
+        memberDS: memberDS,
+        ownerDS: ownerDS,
+        membersAC: membersAC,
+        ownerAC: ownerAC,
+    };
+}
+
+
+
 /**
  * QUICK REPO MENU
  */
@@ -700,6 +872,19 @@
     return compState;
 };
 
+var permNameSort = function(a, b, desc, field) {
+    var a_ = fromHTML(a.getData(field));
+    var b_ = fromHTML(b.getData(field));
+    // extract name from table
+
+    a_ = a_.children[0].innerHTML;
+    b_ = b_.children[0].innerHTML;      
+    
+    var comp = YAHOO.util.Sort.compare;
+    var compState = comp(a_, b_, desc);
+    return compState;
+};
+
 var groupNameSort = function(a, b, desc, field) {
     var a_ = fromHTML(a.getData(field));
     var b_ = fromHTML(b.getData(field));
--- a/rhodecode/templates/_data_table/_dt_elements.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/_data_table/_dt_elements.html	Wed Mar 28 19:54:16 2012 +0200
@@ -40,7 +40,14 @@
   </ul>
 </%def>
 
-<%def name="repo_name(name,rtype,private,fork_of)">
+<%def name="repo_name(name,rtype,private,fork_of,short_name=False, admin=False)">
+    <%
+    def get_name(name,short_name=short_name):
+      if short_name:
+        return name.split('/')[-1]
+      else:
+        return name
+    %>
   <div style="white-space: nowrap">
    ##TYPE OF REPO
    %if h.is_hg(rtype):
@@ -57,7 +64,11 @@
    %endif
 
    ##NAME
-   ${h.link_to(name,h.url('summary_home',repo_name=name),class_="repo_name")}
+   %if admin:
+    ${h.link_to(get_name(name),h.url('edit_repo',repo_name=name),class_="repo_name")}
+   %else:
+    ${h.link_to(get_name(name),h.url('summary_home',repo_name=name),class_="repo_name")}
+   %endif
    %if fork_of:
         <a href="${h.url('summary_home',repo_name=fork_of)}">
         <img class="icon" alt="${_('fork')}" title="${_('Fork of')} ${fork_of}" src="${h.url('/images/icons/arrow_divide.png')}"/></a>
--- a/rhodecode/templates/admin/repos/repo_add_base.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/admin/repos/repo_add_base.html	Wed Mar 28 19:54:16 2012 +0200
@@ -21,6 +21,7 @@
             </div>
             <div class="input">
                 ${h.text('clone_uri',class_="small")}
+                <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
             </div>
          </div>
          <div class="field">
@@ -28,7 +29,8 @@
                  <label for="repo_group">${_('Repository group')}:</label>
              </div>
              <div class="input">
-                 ${h.select('repo_group','',c.repo_groups,class_="medium")}
+                 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
+                 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
              </div>
          </div>
         <div class="field">
@@ -37,6 +39,7 @@
             </div>
             <div class="input">
                 ${h.select('repo_type','hg',c.backends,class_="small")}
+                <span class="help-block">${_('Type of repository to create.')}</span>
             </div>
          </div>
         <div class="field">
@@ -44,15 +47,17 @@
                 <label for="description">${_('Description')}:</label>
             </div>
             <div class="textarea text-area editor">
-                ${h.textarea('description',cols=23,rows=5)}
+                ${h.textarea('description')}
+                <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
             </div>
          </div>
         <div class="field">
             <div class="label label-checkbox">
-                <label for="private">${_('Private')}:</label>
+                <label for="private">${_('Private repository')}:</label>
             </div>
             <div class="checkboxes">
                 ${h.checkbox('private',value="True")}
+                <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
             </div>
          </div>
         <div class="buttons">
--- a/rhodecode/templates/admin/repos/repo_edit.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/admin/repos/repo_edit.html	Wed Mar 28 19:54:16 2012 +0200
@@ -41,6 +41,7 @@
 	           </div>
 	           <div class="input">
 	               ${h.text('clone_uri',class_="medium")}
+                 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
 	           </div>
 	        </div>
 	        <div class="field">
@@ -49,6 +50,7 @@
 	            </div>
 	            <div class="input">
 	                ${h.select('repo_group','',c.repo_groups,class_="medium")}
+                    <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
 	            </div>
 	        </div>
             <div class="field">
@@ -64,16 +66,18 @@
                     <label for="description">${_('Description')}:</label>
                 </div>
                 <div class="textarea text-area editor">
-                    ${h.textarea('description',cols=23,rows=5)}
+                    ${h.textarea('description')}
+                    <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
                 </div>
             </div>
 
             <div class="field">
                 <div class="label label-checkbox">
-                    <label for="private">${_('Private')}:</label>
+                    <label for="private">${_('Private repository')}:</label>
                 </div>
                 <div class="checkboxes">
                     ${h.checkbox('private',value="True")}
+                    <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
                 </div>
             </div>
             <div class="field">
@@ -82,6 +86,7 @@
                 </div>
                 <div class="checkboxes">
                     ${h.checkbox('enable_statistics',value="True")}
+                    <span class="help-block">${_('Enable statistics window on summary page.')}</span>
                 </div>
             </div>
             <div class="field">
@@ -90,15 +95,17 @@
                 </div>
                 <div class="checkboxes">
                     ${h.checkbox('enable_downloads',value="True")}
+                    <span class="help-block">${_('Enable download menu on summary page.')}</span>
                 </div>
             </div>
             <div class="field">
                 <div class="label">
                     <label for="user">${_('Owner')}:</label>
                 </div>
-                <div class="input input-small ac">
+                <div class="input input-medium ac">
                     <div class="perm_ac">
                        ${h.text('user',class_='yui-ac-input')}
+                       <span class="help-block">${_('Change owner of this repository.')}</span>
                        <div id="owner_container"></div>
                     </div>
                 </div>
--- a/rhodecode/templates/admin/repos/repo_edit_perms.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/admin/repos/repo_edit_perms.html	Wed Mar 28 19:54:16 2012 +0200
@@ -25,7 +25,7 @@
             <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
             <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
             <td style="white-space: nowrap;">
-                <img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${r2p.user.username}
+                <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username}
             </td>
             <td>
               %if r2p.user.username !='default':
@@ -46,7 +46,7 @@
             <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
             <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
             <td style="white-space: nowrap;">
-                <img  style="vertical-align:bottom" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
+                <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
             </td>
             <td>
                 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
@@ -117,165 +117,12 @@
         YUD.setStyle('add_perm', 'opacity', '0.6');
         YUD.setStyle('add_perm', 'cursor', 'default');
     });
+    MembersAutoComplete(
+    		  ${c.users_array|n},
+    		  ${c.users_groups_array|n},
+    		  "${_('Group')}",
+    		  "${_('members')}"
+    		);
 });
 
-YAHOO.example.FnMultipleFields = function () {
-    var myUsers = ${c.users_array|n};
-    var myGroups = ${c.users_groups_array|n};
-
-    // Define a custom search function for the DataSource of users
-    var matchUsers = function (sQuery) {
-            // Case insensitive matching
-            var query = sQuery.toLowerCase();
-            var i = 0;
-            var l = myUsers.length;
-            var matches = [];
-
-            // Match against each name of each contact
-            for (; i < l; i++) {
-                contact = myUsers[i];
-                if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
-                    matches[matches.length] = contact;
-                }
-            }
-            return matches;
-        };
-
-    // Define a custom search function for the DataSource of usersGroups
-    var matchGroups = function (sQuery) {
-            // Case insensitive matching
-            var query = sQuery.toLowerCase();
-            var i = 0;
-            var l = myGroups.length;
-            var matches = [];
-
-            // Match against each name of each contact
-            for (; i < l; i++) {
-                matched_group = myGroups[i];
-                if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
-                    matches[matches.length] = matched_group;
-                }
-            }
-            return matches;
-        };
-
-    //match all
-    var matchAll = function (sQuery) {
-            u = matchUsers(sQuery);
-            g = matchGroups(sQuery);
-            return u.concat(g);
-        };
-
-    // DataScheme for members
-    var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
-    memberDS.responseSchema = {
-        fields: ["id", "fname", "lname", "nname", "grname", "grmembers"]
-    };
-
-    // DataScheme for owner
-    var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
-    ownerDS.responseSchema = {
-        fields: ["id", "fname", "lname", "nname"]
-    };
-
-    // Instantiate AutoComplete for perms
-    var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
-    membersAC.useShadow = false;
-    membersAC.resultTypeList = false;
-
-    // Instantiate AutoComplete for owner
-    var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
-    ownerAC.useShadow = false;
-    ownerAC.resultTypeList = false;
-
-
-    // Helper highlight function for the formatter
-    var highlightMatch = function (full, snippet, matchindex) {
-            return full.substring(0, matchindex) + "<span class='match'>" + full.substr(matchindex, snippet.length) + "</span>" + full.substring(matchindex + snippet.length);
-        };
-
-    // Custom formatter to highlight the matching letters
-    var custom_formatter = function (oResultData, sQuery, sResultMatch) {
-            var query = sQuery.toLowerCase();
-
-            if (oResultData.grname != undefined) {
-                var grname = oResultData.grname;
-                var grmembers = oResultData.grmembers;
-                var grnameMatchIndex = grname.toLowerCase().indexOf(query);
-                var grprefix = "${_('Group')}: ";
-                var grsuffix = " (" + grmembers + "  ${_('members')})";
-
-                if (grnameMatchIndex > -1) {
-                    return grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix;
-                }
-
-                return grprefix + oResultData.grname + grsuffix;
-            } else if (oResultData.fname != undefined) {
-
-                var fname = oResultData.fname,
-                    lname = oResultData.lname,
-                    nname = oResultData.nname || "",
-                    // Guard against null value
-                    fnameMatchIndex = fname.toLowerCase().indexOf(query),
-                    lnameMatchIndex = lname.toLowerCase().indexOf(query),
-                    nnameMatchIndex = nname.toLowerCase().indexOf(query),
-                    displayfname, displaylname, displaynname;
-
-                if (fnameMatchIndex > -1) {
-                    displayfname = highlightMatch(fname, query, fnameMatchIndex);
-                } else {
-                    displayfname = fname;
-                }
-
-                if (lnameMatchIndex > -1) {
-                    displaylname = highlightMatch(lname, query, lnameMatchIndex);
-                } else {
-                    displaylname = lname;
-                }
-
-                if (nnameMatchIndex > -1) {
-                    displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
-                } else {
-                    displaynname = nname ? "(" + nname + ")" : "";
-                }
-
-                return displayfname + " " + displaylname + " " + displaynname;
-            } else {
-                return '';
-            }
-        };
-    membersAC.formatResult = custom_formatter;
-    ownerAC.formatResult = custom_formatter;
-
-    var myHandler = function (sType, aArgs) {
-
-            var myAC = aArgs[0]; // reference back to the AC instance
-            var elLI = aArgs[1]; // reference to the selected LI element
-            var oData = aArgs[2]; // object literal of selected item's result data
-            //fill the autocomplete with value
-            if (oData.nname != undefined) {
-                //users
-                myAC.getInputEl().value = oData.nname;
-                YUD.get('perm_new_member_type').value = 'user';
-            } else {
-                //groups
-                myAC.getInputEl().value = oData.grname;
-                YUD.get('perm_new_member_type').value = 'users_group';
-            }
-
-        };
-
-    membersAC.itemSelectEvent.subscribe(myHandler);
-    if(ownerAC.itemSelectEvent){
-    	ownerAC.itemSelectEvent.subscribe(myHandler);
-    }
-
-    return {
-        memberDS: memberDS,
-        ownerDS: ownerDS,
-        membersAC: membersAC,
-        ownerAC: ownerAC,
-    };
-}();
-
 </script>
--- a/rhodecode/templates/admin/repos/repos.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/admin/repos/repos.html	Wed Mar 28 19:54:16 2012 +0200
@@ -48,7 +48,7 @@
                 ${dt.quick_menu(repo['name'])}
               </td>
               <td class="reponame">
-                ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
+                ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'), admin=True)}
               </td>
               ##DESCRIPTION
               <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
--- a/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html	Wed Mar 28 19:54:16 2012 +0200
@@ -15,7 +15,7 @@
             <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
             <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
             <td style="white-space: nowrap;">
-                <img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${r2p.user.username}
+                <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username}
             </td>
             <td>
               %if r2p.user.username !='default':
@@ -35,7 +35,7 @@
             <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
             <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
             <td style="white-space: nowrap;">
-                <img  style="vertical-align:bottom" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
+                <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
             </td>
             <td>
                 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
@@ -68,7 +68,7 @@
 </table>
 <script type="text/javascript">
 function ajaxActionUser(user_id, field_id) {
-    var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.name)}";
+    var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.group_name)}";
     var callback = {
         success: function (o) {
             var tr = YUD.get(String(field_id));
@@ -83,7 +83,7 @@
 };
 
 function ajaxActionUsersGroup(users_group_id,field_id){
-    var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.name)}";
+    var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.group_name)}";
     var callback = {
         success:function(o){
             var tr = YUD.get(String(field_id));
@@ -106,165 +106,12 @@
         YUD.setStyle('add_perm', 'opacity', '0.6');
         YUD.setStyle('add_perm', 'cursor', 'default');
     });
+    MembersAutoComplete(
+            ${c.users_array|n},
+            ${c.users_groups_array|n},
+            "${_('Group')}",
+            "${_('members')}"
+          );
 });
 
-YAHOO.example.FnMultipleFields = function () {
-    var myUsers = ${c.users_array|n};
-    var myGroups = ${c.users_groups_array|n};
-
-    // Define a custom search function for the DataSource of users
-    var matchUsers = function (sQuery) {
-            // Case insensitive matching
-            var query = sQuery.toLowerCase();
-            var i = 0;
-            var l = myUsers.length;
-            var matches = [];
-
-            // Match against each name of each contact
-            for (; i < l; i++) {
-                contact = myUsers[i];
-                if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
-                    matches[matches.length] = contact;
-                }
-            }
-            return matches;
-        };
-
-    // Define a custom search function for the DataSource of usersGroups
-    var matchGroups = function (sQuery) {
-            // Case insensitive matching
-            var query = sQuery.toLowerCase();
-            var i = 0;
-            var l = myGroups.length;
-            var matches = [];
-
-            // Match against each name of each contact
-            for (; i < l; i++) {
-                matched_group = myGroups[i];
-                if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
-                    matches[matches.length] = matched_group;
-                }
-            }
-            return matches;
-        };
-
-    //match all
-    var matchAll = function (sQuery) {
-            u = matchUsers(sQuery);
-            g = matchGroups(sQuery);
-            return u.concat(g);
-        };
-
-    // DataScheme for members
-    var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
-    memberDS.responseSchema = {
-        fields: ["id", "fname", "lname", "nname", "grname", "grmembers"]
-    };
-
-    // DataScheme for owner
-    var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
-    ownerDS.responseSchema = {
-        fields: ["id", "fname", "lname", "nname"]
-    };
-
-    // Instantiate AutoComplete for perms
-    var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
-    membersAC.useShadow = false;
-    membersAC.resultTypeList = false;
-
-    // Instantiate AutoComplete for owner
-    var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
-    ownerAC.useShadow = false;
-    ownerAC.resultTypeList = false;
-
-
-    // Helper highlight function for the formatter
-    var highlightMatch = function (full, snippet, matchindex) {
-            return full.substring(0, matchindex) + "<span class='match'>" + full.substr(matchindex, snippet.length) + "</span>" + full.substring(matchindex + snippet.length);
-        };
-
-    // Custom formatter to highlight the matching letters
-    var custom_formatter = function (oResultData, sQuery, sResultMatch) {
-            var query = sQuery.toLowerCase();
-
-            if (oResultData.grname != undefined) {
-                var grname = oResultData.grname;
-                var grmembers = oResultData.grmembers;
-                var grnameMatchIndex = grname.toLowerCase().indexOf(query);
-                var grprefix = "${_('Group')}: ";
-                var grsuffix = " (" + grmembers + "  ${_('members')})";
-
-                if (grnameMatchIndex > -1) {
-                    return grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix;
-                }
-
-                return grprefix + oResultData.grname + grsuffix;
-            } else if (oResultData.fname != undefined) {
-
-                var fname = oResultData.fname,
-                    lname = oResultData.lname,
-                    nname = oResultData.nname || "",
-                    // Guard against null value
-                    fnameMatchIndex = fname.toLowerCase().indexOf(query),
-                    lnameMatchIndex = lname.toLowerCase().indexOf(query),
-                    nnameMatchIndex = nname.toLowerCase().indexOf(query),
-                    displayfname, displaylname, displaynname;
-
-                if (fnameMatchIndex > -1) {
-                    displayfname = highlightMatch(fname, query, fnameMatchIndex);
-                } else {
-                    displayfname = fname;
-                }
-
-                if (lnameMatchIndex > -1) {
-                    displaylname = highlightMatch(lname, query, lnameMatchIndex);
-                } else {
-                    displaylname = lname;
-                }
-
-                if (nnameMatchIndex > -1) {
-                    displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
-                } else {
-                    displaynname = nname ? "(" + nname + ")" : "";
-                }
-
-                return displayfname + " " + displaylname + " " + displaynname;
-            } else {
-                return '';
-            }
-        };
-    membersAC.formatResult = custom_formatter;
-    ownerAC.formatResult = custom_formatter;
-
-    var myHandler = function (sType, aArgs) {
-
-            var myAC = aArgs[0]; // reference back to the AC instance
-            var elLI = aArgs[1]; // reference to the selected LI element
-            var oData = aArgs[2]; // object literal of selected item's result data
-            //fill the autocomplete with value
-            if (oData.nname != undefined) {
-                //users
-                myAC.getInputEl().value = oData.nname;
-                YUD.get('perm_new_member_type').value = 'user';
-            } else {
-                //groups
-                myAC.getInputEl().value = oData.grname;
-                YUD.get('perm_new_member_type').value = 'users_group';
-            }
-
-        };
-
-    membersAC.itemSelectEvent.subscribe(myHandler);
-    if(ownerAC.itemSelectEvent){
-        ownerAC.itemSelectEvent.subscribe(myHandler);
-    }
-
-    return {
-        memberDS: memberDS,
-        ownerDS: ownerDS,
-        membersAC: membersAC,
-        ownerAC: ownerAC,
-    };
-}();
-
 </script>
--- a/rhodecode/templates/admin/repos_groups/repos_groups.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/admin/repos_groups/repos_groups.html	Wed Mar 28 19:54:16 2012 +0200
@@ -17,5 +17,5 @@
     ${self.menu('admin')}
 </%def>
 <%def name="main()">
-        <%include file="/index_base.html" args="parent=self"/>
+        <%include file="/index_base.html" args="parent=self,short_repo_names=True"/>
 </%def>
--- a/rhodecode/templates/admin/repos_groups/repos_groups_show.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/admin/repos_groups/repos_groups_show.html	Wed Mar 28 19:54:16 2012 +0200
@@ -44,7 +44,7 @@
                       <td>
                           <div style="white-space: nowrap">
                           <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
-                          ${h.link_to(h.literal(' &raquo; '.join([g.name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))}
+                          ${h.link_to(h.literal(' &raquo; '.join(map(h.safe_unicode,[g.name for g in gr.parents+[gr]]))),url('edit_repos_group',id=gr.group_id))}
                           </div>
                       </td>
                       <td>${gr.group_description}</td>
--- a/rhodecode/templates/admin/users/user_edit_my_account.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/admin/users/user_edit_my_account.html	Wed Mar 28 19:54:16 2012 +0200
@@ -113,52 +113,45 @@
     <div class="title">
         <h5>
         <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
-        ${_('My repositories')}
+        <a id="show_my" class="link-white" href="#my">${_('My repos')}</a> / <a id="show_perms" class="link-white" href="#perms">${_('My permissions')}</a>
         </h5>
          %if h.HasPermissionAny('hg.admin','hg.create.repository')():
          <ul class="links">
            <li>
-             <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
+             <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
            </li>
          </ul>
          %endif
     </div>
     <!-- end box / title -->
-    <div class="table">
-	    <table>
+    <div id="my" class="table">
+        <div id='repos_list_wrap' class="yui-skin-sam">
+        <table id="repos_list">
 	    <thead>
             <tr>
+            <th></th>
             <th class="left">${_('Name')}</th>
-            <th class="left">${_('revision')}</th>
-            <th colspan="2" class="left">${_('action')}</th>
+            <th class="left">${_('Revision')}</th>
+            <th class="left">${_('Action')}</th>
+            <th class="left">${_('Action')}</th>
 	    </thead>
 	     <tbody>
+         <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
 	     %if c.user_repos:
 		     %for repo in c.user_repos:
 		        <tr>
-		            <td>
-                     %if h.is_hg(repo['dbrepo']['repo_type']):
-                       <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
-                     %elif h.is_git(repo['dbrepo']['repo_type']):
-                       <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
-                     %else:
-
-                     %endif
-		             %if repo['dbrepo']['private']:
-		                <img class="icon" alt="${_('private')}" src="${h.url('/images/icons/lock.png')}"/>
-		             %else:
-		                <img class="icon" alt="${_('public')}" src="${h.url('/images/icons/lock_open.png')}"/>
-		             %endif
-
-		            ${h.link_to(repo['name'], h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
-		            %if repo['dbrepo_fork']:
-		            	<a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
-		            	<img class="icon" alt="${_('public')}"
-		            	title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
-		            	src="${h.url('/images/icons/arrow_divide.png')}"/></a>
-		            %endif
-		            </td>
-		            <td><span class="tooltip" title="${repo['last_change']}">${("r%s:%s") % (repo['rev'],h.short_id(repo['tip']))}</span></td>
+                    ##QUICK MENU
+                    <td class="quick_repo_menu">
+                      ${dt.quick_menu(repo['name'])}
+                    </td>
+                    ##REPO NAME AND ICONS
+                    <td class="reponame">
+                      ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
+                    </td>
+                    ##LAST REVISION
+                    <td>
+                        ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
+                    </td>
 		            <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url('/images/icons/application_form_edit.png')}"/></a></td>
 		            <td>
 	                  ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
@@ -177,14 +170,144 @@
 	     %endif
 	     </tbody>
 	     </table>
+       </div>
+    </div>
+    <div id="perms" class="table" style="display:none">
+           %for section in sorted(c.rhodecode_user.permissions.keys()):
+            <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
+
+            <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
+            <table id="tbl_list_${section}">
+              <thead>
+                  <tr>
+                  <th class="left">${_('Name')}</th>
+                  <th class="left">${_('Permission')}</th>
+              </thead>
+              <tbody>
+            %for k in c.rhodecode_user.permissions[section]:
+           <%
+           if section != 'global':
+               section_perm = c.rhodecode_user.permissions[section].get(k)
+               _perm = section_perm.split('.')[-1]
+           else:
+               _perm = section_perm = None
+           %>
+            %if _perm not in ['none']:
+                <tr>
+                    <td>
+                        %if section == 'repositories':
+                            <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
+                        %elif section == 'repositories_groups':
+                            <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
+                        %else:
+                            ${k}
+                        %endif
+                    </td>
+                    <td>
+                        %if section == 'global':
+                         ${h.bool2icon(True)}
+                        %else:
+                        <span class="perm_tag ${_perm}">${section_perm}</span>
+                        %endif
+                     </td>
+                </tr>
+             %endif
+            %endfor
+            </tbody>
+            </table>
+            </div>
+           %endfor
     </div>
 </div>
 <script type="text/javascript">
-var nodes = YUQ('div.table tr td a.repo_name');
-var target = 'q_filter';
-var func = function(node){
-    return node.parentNode.parentNode;
+var filter_activate = function(){
+    var nodes = YUQ('#my tr td a.repo_name');
+    var func = function(node){
+        return node.parentNode.parentNode.parentNode.parentNode;
+    }
+    q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
 }
-q_filter(target,nodes,func);
+
+YUE.on('show_my','click',function(e){
+    YUD.setStyle('perms','display','none');
+    YUD.setStyle('my','display','');
+    YUD.get('q_filter').removeAttribute('disabled');
+    filter_activate();
+    YUE.preventDefault(e);
+})
+YUE.on('show_perms','click',function(e){
+    YUD.setStyle('my','display','none');
+    YUD.setStyle('perms','display','');
+    YUD.setAttribute('q_filter','disabled','disabled');
+    YUE.preventDefault(e);
+})
+
+
+// main table sorting
+var myColumnDefs = [
+    {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
+    {key:"name",label:"${_('Name')}",sortable:true,
+        sortOptions: { sortFunction: nameSort }},
+    {key:"tip",label:"${_('Tip')}",sortable:true,
+        sortOptions: { sortFunction: revisionSort }},
+    {key:"action1",label:"",sortable:false},
+    {key:"action2",label:"",sortable:false},
+];
+
+var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
+myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
+myDataSource.responseSchema = {
+    fields: [
+        {key:"menu"},
+        {key:"name"},
+        {key:"tip"},
+        {key:"action1"},
+        {key:"action2"},
+    ]
+};
+var trans_defs =  {
+    sortedBy:{key:"name",dir:"asc"},
+    MSG_SORTASC:"${_('Click to sort ascending')}",
+    MSG_SORTDESC:"${_('Click to sort descending')}",
+    MSG_EMPTY:"${_('No records found.')}",
+    MSG_ERROR:"${_('Data error.')}",
+    MSG_LOADING:"${_('Loading...')}",
+}
+var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,trans_defs);
+myDataTable.subscribe('postRenderEvent',function(oArgs) {
+    tooltip_activate();
+    quick_repo_menu();
+    filter_activate();
+});
+
+var permsColumnDefs = [
+    {key:"name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: permNameSort }},
+    {key:"perm",label:"${_('Permission')}",sortable:false,},
+];
+
+// perms repos table
+var myDataSource2 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories"));
+myDataSource2.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
+myDataSource2.responseSchema = {
+    fields: [
+        {key:"name"},
+        {key:"perm"},
+    ]
+};
+
+new YAHOO.widget.DataTable("tbl_list_wrap_repositories", permsColumnDefs, myDataSource2, trans_defs);
+
+//perms groups table
+var myDataSource3 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories_groups"));
+myDataSource3.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
+myDataSource3.responseSchema = {
+    fields: [
+        {key:"name"},
+        {key:"perm"},
+    ]
+};
+
+new YAHOO.widget.DataTable("tbl_list_wrap_repositories_groups", permsColumnDefs, myDataSource3, trans_defs);
+
 </script>
 </%def>
--- a/rhodecode/templates/admin/users_groups/users_groups.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/admin/users_groups/users_groups.html	Wed Mar 28 19:54:16 2012 +0200
@@ -37,7 +37,7 @@
             %for cnt,u_group in enumerate(c.users_groups_list):
                 <tr class="parity${cnt%2}">
                     <td>${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))}</td>
-                    <td><span class="tooltip" title="${', '.join([x.user.username for x in u_group.members[:50]])}">${len(u_group.members)}</span></td>
+                    <td><span class="tooltip" title="${', '.join(map(h.safe_unicode,[x.user.username for x in u_group.members[:50]]))}">${len(u_group.members)}</span></td>
                     <td>${h.bool2icon(u_group.users_group_active)}</td>
                     <td>
                         ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
--- a/rhodecode/templates/base/root.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/base/root.html	Wed Mar 28 19:54:16 2012 +0200
@@ -1,5 +1,5 @@
 ## -*- coding: utf-8 -*-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml">
     <head>
         <title>${self.title()}</title>
@@ -37,7 +37,7 @@
         ## JAVASCRIPT ##
         <%def name="js()">
             <script type="text/javascript" src="${h.url('/js/yui.2.9.js')}"></script>
-            <!--[if IE]>
+            <!--[if lt IE 9]>
                <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
             <![endif]-->
             <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
@@ -130,6 +130,17 @@
         ${self.js()}
     </head>
     <body id="body">
-        ${next.body()}
+     ## IE hacks
+      <!--[if IE 7]>
+      <script>YUD.addClass(document.body,'ie7')</script>
+      <![endif]-->            
+      <!--[if IE 8]>
+      <script>YUD.addClass(document.body,'ie8')</script>
+      <![endif]-->
+      <!--[if IE 9]>
+      <script>YUD.addClass(document.body,'ie9')</script>
+      <![endif]-->    
+
+      ${next.body()}
     </body>
 </html>
--- a/rhodecode/templates/changelog/changelog.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/changelog/changelog.html	Wed Mar 28 19:54:16 2012 +0200
@@ -93,11 +93,11 @@
 									%endif
 									%if h.is_hg(c.rhodecode_repo) and cs.branch:
 									<span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
-									   ${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
+									   ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
 									%endif
 									%for tag in cs.tags:
 										<span class="tagtag"  title="${'%s %s' % (_('tag'),tag)}">
-										${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
+										${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
 									%endfor
 								</span>
 						</div>
--- a/rhodecode/templates/changeset/changeset.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/changeset/changeset.html	Wed Mar 28 19:54:16 2012 +0200
@@ -36,8 +36,8 @@
                 <div class="diff-actions">
                   <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
                   <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
-                  ${c.ignorews_url()}
-                  ${c.context_url()}
+                  ${c.ignorews_url(request.GET)}
+                  ${c.context_url(request.GET)}
                 </div>
                 <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
 			</div>
@@ -91,14 +91,14 @@
 	                </div>
 	        </div>
 	        <span>
-	        ${_('%s files affected with %s additions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
+	        ${_('%s files affected with %s insertions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
 	        </span>
 	        <div class="cs_files">
 	                %for change,filenode,diff,cs1,cs2,stat in c.changes:
 	                    <div class="cs_${change}">
                             <div class="node">
                             %if change != 'removed':
-                                ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path)+"_target")}
+                                ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path,request.GET)+"_target")}
                             %else:
                                 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
                             %endif
--- a/rhodecode/templates/changeset/diff_block.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/changeset/diff_block.html	Wed Mar 28 19:54:16 2012 +0200
@@ -7,7 +7,7 @@
 
 %for change,filenode,diff,cs1,cs2,stat in changes:
     %if change !='removed':
-    <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}_target" style="clear:both;height:90px;margin-top:-60px"></div>
+    <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}_target" style="clear:both;margin-top:25px"></div>
     <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" class="diffblock  margined comm">
         <div class="code-header">
             <div class="changeset_header">
@@ -19,8 +19,8 @@
                   <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" title="${_('diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/></a>
                   <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
                   <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
-                  ${c.ignorews_url(h.FID(filenode.changeset.raw_id,filenode.path))}
-                  ${c.context_url(h.FID(filenode.changeset.raw_id,filenode.path))}
+                  ${c.ignorews_url(request.GET, h.FID(filenode.changeset.raw_id,filenode.path))}
+                  ${c.context_url(request.GET, h.FID(filenode.changeset.raw_id,filenode.path))}
                 </div>
                 <span style="float:right;margin-top:-3px">
                   <label>
--- a/rhodecode/templates/index.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/index.html	Wed Mar 28 19:54:16 2012 +0200
@@ -4,5 +4,5 @@
 <%def name="breadcrumbs()"></%def>
 <%def name="page_nav()">${self.menu('home')}</%def>
 <%def name="main()">
-    	<%include file="index_base.html" args="parent=self"/>
+   <%include file="index_base.html" args="parent=self"/>
 </%def>
--- a/rhodecode/templates/index_base.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/index_base.html	Wed Mar 28 19:54:16 2012 +0200
@@ -9,7 +9,11 @@
                 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
                 <ul class="links">
                   <li>
+                  %if c.group:
+                    <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository',parent_group=c.group.group_id))}</span>
+                  %else:
                     <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
+                  %endif
                   </li>
                 </ul>
                 %endif
@@ -77,7 +81,7 @@
                     </td>
                     ##REPO NAME AND ICONS
                     <td class="reponame">
-                      ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
+                      ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'),pageargs.get('short_repo_names'))}
                     </td>
                     ##DESCRIPTION
                     <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
@@ -115,7 +119,7 @@
         </div>
     </div>
     <script>
-      YUD.get('repo_count').innerHTML = ${cnt+1};
+      YUD.get('repo_count').innerHTML = ${cnt};
       var func = function(node){
           return node.parentNode.parentNode.parentNode.parentNode;
       }
--- a/rhodecode/templates/journal/journal.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/journal/journal.html	Wed Mar 28 19:54:16 2012 +0200
@@ -48,7 +48,7 @@
                   <tr>
                   <th></th>
                   <th class="left">${_('Name')}</th>
-                  <th class="left">${_('Tip')}</th>
+                  <th class="left">${_('Revision')}</th>
                   <th class="left">${_('Action')}</th>
                   <th class="left">${_('Action')}</th>
              </thead>
--- a/rhodecode/templates/settings/repo_settings.html	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/templates/settings/repo_settings.html	Wed Mar 28 19:54:16 2012 +0200
@@ -34,12 +34,22 @@
                     ${h.text('repo_name',class_="small")}
                 </div>
              </div>
+	       <div class="field">
+	           <div class="label">
+	               <label for="clone_uri">${_('Clone uri')}:</label>
+	           </div>
+	           <div class="input">
+	               ${h.text('clone_uri',class_="medium")}
+                 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
+	           </div>
+	        </div>
             <div class="field">
                 <div class="label">
                     <label for="repo_group">${_('Repository group')}:</label>
                 </div>
                 <div class="input">
                     ${h.select('repo_group','',c.repo_groups,class_="medium")}
+                    <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
                 </div>
             </div>
             <div class="field">
@@ -47,16 +57,18 @@
                     <label for="description">${_('Description')}:</label>
                 </div>
                 <div class="textarea text-area editor">
-                    ${h.textarea('description',cols=23,rows=5)}
+                    ${h.textarea('description')}
+                    <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
                 </div>
             </div>
 
             <div class="field">
                 <div class="label label-checkbox">
-                    <label for="private">${_('Private')}:</label>
+                    <label for="private">${_('Private repository')}:</label>
                 </div>
                 <div class="checkboxes">
                     ${h.checkbox('private',value="True")}
+                    <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
                 </div>
             </div>
 
--- a/rhodecode/tests/functional/test_changeset_comments.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/tests/functional/test_changeset_comments.py	Wed Mar 28 19:54:16 2012 +0200
@@ -2,7 +2,8 @@
 from rhodecode.model.db import ChangesetComment, Notification, User, \
     UserNotification
 
-class TestChangeSetCommentrController(TestController):
+
+class TestChangeSetCommentsController(TestController):
 
     def setUp(self):
         for x in ChangesetComment.query().all():
@@ -27,7 +28,7 @@
         rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
         text = u'CommentOnRevision'
 
-        params = {'text':text}
+        params = {'text': text}
         response = self.app.post(url(controller='changeset', action='comment',
                                      repo_name=HG_REPO, revision=rev),
                                      params=params)
@@ -42,13 +43,18 @@
         self.assertTrue('''<div class="comments-number">%s '''
                         '''comment(s) (0 inline)</div>''' % 1 in response.body)
 
+        self.assertEqual(Notification.query().count(), 1)
+        self.assertEqual(ChangesetComment.query().count(), 1)
 
-        self.assertEqual(Notification.query().count(), 1)
         notification = Notification.query().all()[0]
 
-        self.assertEqual(notification.type_, Notification.TYPE_CHANGESET_COMMENT)
-        self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
-                          'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
+        ID = ChangesetComment.query().first().comment_id
+        self.assertEqual(notification.type_,
+                         Notification.TYPE_CHANGESET_COMMENT)
+        sbj = (u'/vcs_test_hg/changeset/'
+               '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % ID)
+        print "%s vs %s" % (sbj, notification.subject)
+        self.assertTrue(sbj in notification.subject)
 
     def test_create_inline(self):
         self.log_user()
@@ -57,7 +63,7 @@
         f_path = 'vcs/web/simplevcs/views/repository.py'
         line = 'n1'
 
-        params = {'text':text, 'f_path':f_path, 'line':line}
+        params = {'text': text, 'f_path': f_path, 'line': line}
         response = self.app.post(url(controller='changeset', action='comment',
                                      repo_name=HG_REPO, revision=rev),
                                      params=params)
@@ -76,11 +82,16 @@
                         '''repositorypy">''' in response.body)
 
         self.assertEqual(Notification.query().count(), 1)
-        notification = Notification.query().all()[0]
+        self.assertEqual(ChangesetComment.query().count(), 1)
 
-        self.assertEqual(notification.type_, Notification.TYPE_CHANGESET_COMMENT)
-        self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
-                          'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
+        notification = Notification.query().all()[0]
+        ID = ChangesetComment.query().first().comment_id
+        self.assertEqual(notification.type_,
+                         Notification.TYPE_CHANGESET_COMMENT)
+        sbj = (u'/vcs_test_hg/changeset/'
+               '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % ID)
+        print "%s vs %s" % (sbj, notification.subject)
+        self.assertTrue(sbj in notification.subject)
 
     def test_create_with_mention(self):
         self.log_user()
@@ -103,7 +114,6 @@
         self.assertTrue('''<div class="comments-number">%s '''
                         '''comment(s) (0 inline)</div>''' % 1 in response.body)
 
-
         self.assertEqual(Notification.query().count(), 2)
         users = [x.user.username for x in UserNotification.query().all()]
 
@@ -115,7 +125,7 @@
         rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
         text = u'CommentOnRevision'
 
-        params = {'text':text}
+        params = {'text': text}
         response = self.app.post(url(controller='changeset', action='comment',
                                      repo_name=HG_REPO, revision=rev),
                                      params=params)
@@ -124,7 +134,6 @@
         self.assertEqual(len(comments), 1)
         comment_id = comments[0].comment_id
 
-
         self.app.delete(url(controller='changeset',
                                     action='delete_comment',
                                     repo_name=HG_REPO,
--- a/rhodecode/tests/functional/test_login.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/tests/functional/test_login.py	Wed Mar 28 19:54:16 2012 +0200
@@ -1,10 +1,11 @@
 # -*- coding: utf-8 -*-
 from rhodecode.tests import *
 from rhodecode.model.db import User, Notification
-from rhodecode.lib import generate_api_key
+from rhodecode.lib.utils2 import generate_api_key
 from rhodecode.lib.auth import check_password
 from rhodecode.model.meta import Session
 
+
 class TestLoginController(TestController):
 
     def tearDown(self):
--- a/rhodecode/tests/test_libs.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/tests/test_libs.py	Wed Mar 28 19:54:16 2012 +0200
@@ -65,22 +65,20 @@
 
 class TestLibs(unittest.TestCase):
 
-
     def test_uri_filter(self):
-        from rhodecode.lib import uri_filter
+        from rhodecode.lib.utils2 import uri_filter
 
         for url in TEST_URLS:
             self.assertEqual(uri_filter(url[0]), url[1])
 
     def test_credentials_filter(self):
-        from rhodecode.lib import credentials_filter
+        from rhodecode.lib.utils2 import credentials_filter
 
         for url in TEST_URLS:
             self.assertEqual(credentials_filter(url[0]), url[2])
 
-
     def test_str2bool(self):
-        from rhodecode.lib import str2bool
+        from rhodecode.lib.utils2 import str2bool
         test_cases = [
             ('t', True),
             ('true', True),
@@ -103,9 +101,8 @@
         for case in test_cases:
             self.assertEqual(str2bool(case[0]), case[1])
 
-
     def test_mention_extractor(self):
-        from rhodecode.lib import extract_mentioned_users
+        from rhodecode.lib.utils2 import extract_mentioned_users
         sample = ("@first hi there @marcink here's my email marcin@email.com "
                   "@lukaszb check it pls @ ttwelve @D[] @one@two@three "
                   "@MARCIN    @maRCiN @2one_more22")
--- a/rhodecode/tests/test_models.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/rhodecode/tests/test_models.py	Wed Mar 28 19:54:16 2012 +0200
@@ -5,7 +5,7 @@
 from rhodecode.model.repos_group import ReposGroupModel
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
-    UsersGroup, UsersGroupMember, Permission
+    UsersGroup, UsersGroupMember, Permission, UsersGroupRepoGroupToPerm
 from sqlalchemy.exc import IntegrityError
 from rhodecode.model.user import UserModel
 
@@ -430,6 +430,11 @@
             username=u'u1', password=u'qweqwe',
             email=u'u1@rhodecode.org', name=u'u1', lastname=u'u1'
         )
+        self.u2 = UserModel().create_or_update(
+            username=u'u2', password=u'qweqwe',
+            email=u'u2@rhodecode.org', name=u'u2', lastname=u'u2'
+        )
+        self.anon = User.get_by_username('default')
         self.a1 = UserModel().create_or_update(
             username=u'a1', password=u'qweqwe',
             email=u'a1@rhodecode.org', name=u'a1', lastname=u'a1', admin=True
@@ -437,7 +442,10 @@
         Session.commit()
 
     def tearDown(self):
+        if hasattr(self, 'test_repo'):
+            RepoModel().delete(repo=self.test_repo)
         UserModel().delete(self.u1)
+        UserModel().delete(self.u2)
         UserModel().delete(self.a1)
         if hasattr(self, 'g1'):
             ReposGroupModel().delete(self.g1.group_id)
@@ -578,3 +586,130 @@
                          new_perm_h)
         self.assertEqual(u1_auth.permissions['repositories_groups'],
                          perms['repositories_groups'])
+
+    def test_repo_in_group_permissions(self):
+        self.g1 = _make_group('group1', skip_if_exists=True)
+        self.g2 = _make_group('group2', skip_if_exists=True)
+        Session.commit()
+        # both perms should be read !
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        self.assertEqual(u1_auth.permissions['repositories_groups'],
+                         {u'group1': u'group.read', u'group2': u'group.read'})
+
+        a1_auth = AuthUser(user_id=self.anon.user_id)
+        self.assertEqual(a1_auth.permissions['repositories_groups'],
+                 {u'group1': u'group.read', u'group2': u'group.read'})
+
+        #Change perms to none for both groups
+        ReposGroupModel().grant_user_permission(repos_group=self.g1,
+                                                user=self.anon,
+                                                perm='group.none')
+        ReposGroupModel().grant_user_permission(repos_group=self.g2,
+                                                user=self.anon,
+                                                perm='group.none')
+
+
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        self.assertEqual(u1_auth.permissions['repositories_groups'],
+                 {u'group1': u'group.none', u'group2': u'group.none'})
+
+        a1_auth = AuthUser(user_id=self.anon.user_id)
+        self.assertEqual(a1_auth.permissions['repositories_groups'],
+                 {u'group1': u'group.none', u'group2': u'group.none'})
+
+        # add repo to group
+        form_data = {
+            'repo_name':HG_REPO,
+            'repo_name_full':os.path.join(self.g1.group_name,HG_REPO),
+            'repo_type':'hg',
+            'clone_uri':'',
+            'repo_group':self.g1.group_id,
+            'description':'desc',
+            'private':False
+        }
+        self.test_repo = RepoModel().create(form_data, cur_user=self.u1)
+        Session.commit()
+
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        self.assertEqual(u1_auth.permissions['repositories_groups'],
+                 {u'group1': u'group.none', u'group2': u'group.none'})
+
+        a1_auth = AuthUser(user_id=self.anon.user_id)
+        self.assertEqual(a1_auth.permissions['repositories_groups'],
+                 {u'group1': u'group.none', u'group2': u'group.none'})
+
+        #grant permission for u2 !
+        ReposGroupModel().grant_user_permission(repos_group=self.g1,
+                                                user=self.u2,
+                                                perm='group.read')
+        ReposGroupModel().grant_user_permission(repos_group=self.g2,
+                                                user=self.u2,
+                                                perm='group.read')
+        Session.commit()
+        self.assertNotEqual(self.u1, self.u2)
+        #u1 and anon should have not change perms while u2 should !
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        self.assertEqual(u1_auth.permissions['repositories_groups'],
+                 {u'group1': u'group.none', u'group2': u'group.none'})
+
+        u2_auth = AuthUser(user_id=self.u2.user_id)
+        self.assertEqual(u2_auth.permissions['repositories_groups'],
+                 {u'group1': u'group.read', u'group2': u'group.read'})
+
+        a1_auth = AuthUser(user_id=self.anon.user_id)
+        self.assertEqual(a1_auth.permissions['repositories_groups'],
+                 {u'group1': u'group.none', u'group2': u'group.none'})
+
+    def test_repo_group_user_as_user_group_member(self):
+        # create Group1
+        self.g1 = _make_group('group1', skip_if_exists=True)
+        Session.commit()
+        a1_auth = AuthUser(user_id=self.anon.user_id)
+
+        self.assertEqual(a1_auth.permissions['repositories_groups'],
+                         {u'group1': u'group.read'})
+
+        # set default permission to none
+        ReposGroupModel().grant_user_permission(repos_group=self.g1,
+                                                user=self.anon,
+                                                perm='group.none')
+        # make group
+        self.ug1 = UsersGroupModel().create('G1')
+        # add user to group
+        UsersGroupModel().add_user_to_group(self.ug1, self.u1)
+        Session.commit()
+
+        # check if user is in the group
+        membrs = [x.user_id for x in UsersGroupModel().get(self.ug1.users_group_id).members]
+        self.assertEqual(membrs, [self.u1.user_id])
+        # add some user to that group
+
+        # check his permissions
+        a1_auth = AuthUser(user_id=self.anon.user_id)
+        self.assertEqual(a1_auth.permissions['repositories_groups'],
+                         {u'group1': u'group.none'})
+
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        self.assertEqual(u1_auth.permissions['repositories_groups'],
+                         {u'group1': u'group.none'})
+
+        # grant ug1 read permissions for
+        ReposGroupModel().grant_users_group_permission(repos_group=self.g1,
+                                                       group_name=self.ug1,
+                                                       perm='group.read')
+        Session.commit()
+        # check if the
+        obj = Session.query(UsersGroupRepoGroupToPerm)\
+            .filter(UsersGroupRepoGroupToPerm.group == self.g1)\
+            .filter(UsersGroupRepoGroupToPerm.users_group == self.ug1)\
+            .scalar()
+        self.assertEqual(obj.permission.permission_name, 'group.read')
+
+        a1_auth = AuthUser(user_id=self.anon.user_id)
+
+        self.assertEqual(a1_auth.permissions['repositories_groups'],
+                         {u'group1': u'group.none'})
+
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        self.assertEqual(u1_auth.permissions['repositories_groups'],
+                         {u'group1': u'group.read'})
--- a/setup.py	Sat Mar 03 03:41:19 2012 +0200
+++ b/setup.py	Wed Mar 28 19:54:16 2012 +0200
@@ -94,8 +94,9 @@
     main = pylons.util:PylonsInstaller
 
     [paste.global_paster_command]
-    make-index = rhodecode.lib.indexers:MakeIndex
-    upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
+    make-index=rhodecode.lib.indexers:MakeIndex
+    make-rcext=rhodecode.config.rcextensions.make_rcextensions:MakeRcExt
+    upgrade-db=rhodecode.lib.dbmigrate:UpgradeDb
     celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
     """,
 )
--- a/test.ini	Sat Mar 03 03:41:19 2012 +0200
+++ b/test.ini	Wed Mar 28 19:54:16 2012 +0200
@@ -17,6 +17,7 @@
 #error_email_from = paste_error@localhost
 #app_email_from = rhodecode-noreply@localhost
 #error_message =
+#email_prefix = [RhodeCode]
 
 #smtp_server = mail.server.com
 #smtp_username = 
@@ -24,6 +25,8 @@
 #smtp_port = 
 #smtp_use_tls = false
 #smtp_use_ssl = true
+# Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
+#smtp_auth = 
 
 [server:main]
 ##nr of threads to spawn
@@ -53,6 +56,42 @@
 use_gravatar = true
 container_auth_enabled = false
 proxypass_auth_enabled = false
+	
+
+## overwrite schema of clone url
+## available vars:
+## scheme - http/https
+## user - current user
+## pass - password 
+## netloc - network location
+## path - usually repo_name
+
+#clone_uri = {scheme}://{user}{pass}{netloc}{path}
+
+## issue tracking mapping for commits messages
+## comment out issue_pat, issue_server, issue_prefix to enable
+
+## pattern to get the issues from commit messages
+## default one used here is #<numbers> with a regex passive group for `#`
+## {id} will be all groups matched from this pattern
+
+issue_pat = (?:\s*#)(\d+)
+
+## server url to the issue, each {id} will be replaced with match
+## fetched from the regex and {repo} is replaced with repository name
+
+issue_server_link = https://myissueserver.com/{repo}/issue/{id}
+
+## prefix to add to link to indicate it's an url
+## #314 will be replaced by <issue_prefix><id>
+
+issue_prefix = #
+
+## instance-id prefix
+## a prefix key for this instance used for cache invalidation when running 
+## multiple instances of rhodecode, make sure it's globally unique for 
+## all running rhodecode instances. Leave empty if you don't use it
+instance_id = 
 
 ####################################
 ###        CELERY CONFIG        ####
@@ -86,6 +125,7 @@
 ####################################
 beaker.cache.data_dir=/tmp/data/cache/data
 beaker.cache.lock_dir=/tmp/data/cache/lock
+
 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
 
 beaker.cache.super_short_term.type=memory
@@ -118,12 +158,27 @@
 ## Type of storage used for the session, current types are 
 ## dbm, file, memcached, database, and memory. 
 ## The storage uses the Container API 
-##that is also used by the cache system.
-beaker.session.type = file
+## that is also used by the cache system.
+
+## db session example
+
+#beaker.session.type = ext:database
+#beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
+#beaker.session.table_name = db_session 
+
+## encrypted cookie session, good for many instances
+#beaker.session.type = cookie
 
+beaker.session.type = file
 beaker.session.key = rhodecode
-beaker.session.secret = g654dcno0-9873jhgfreyu
+# secure cookie requires AES python libraries
+#beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
+#beaker.session.validate_key = 9712sds2212c--zxc123
 beaker.session.timeout = 36000
+beaker.session.httponly = true
+
+## uncomment for https secure cookie
+beaker.session.secure = false
 
 ##auto save the session to not to use .save()
 beaker.session.auto = False
@@ -131,7 +186,7 @@
 ##true exire at browser close
 #beaker.session.cookie_expires = 3600
 
-    
+
 ################################################################################
 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*  ##
 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to  ##
@@ -151,15 +206,17 @@
 #########################################################
 sqlalchemy.db1.url = sqlite:///%(here)s/test.db
 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
-#sqlalchemy.db1.echo = false
-#sqlalchemy.db1.pool_recycle = 3600
-sqlalchemy.convert_unicode = true
+#sqlalchemy.db1.url = mysql://root:qwe123qwe@localhost/rhodecode_tests
+
+sqlalchemy.db1.echo = false
+sqlalchemy.db1.pool_recycle = 3600
+sqlalchemy.db1.convert_unicode = true
 
 ################################
 ### LOGGING CONFIGURATION   ####
 ################################
 [loggers]
-keys = root, routes, rhodecode, sqlalchemy, beaker, templates
+keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
 
 [handlers]
 keys = console
@@ -205,6 +262,12 @@
 qualname = sqlalchemy.engine
 propagate = 0
 
+[logger_whoosh_indexer]
+level = DEBUG
+handlers = 
+qualname = whoosh_indexer
+propagate = 1
+
 ##############
 ## HANDLERS ##
 ##############