changeset 2031:82a88013a3fd

merge 1.3 into stable
author Marcin Kuzminski <marcin@python-works.com>
date Sun, 26 Feb 2012 17:25:09 +0200
parents ab0e122b38a7 (current diff) 61f9aeb2129e (diff)
children abe75448253c
files .hgignore CONTRIBUTORS README.rst development.ini docs/api/api.rst docs/api/index.rst docs/changelog.rst docs/images/screenshot1_main_page.png docs/images/screenshot2_summary_page.png docs/images/screenshot3_changelog_page.png docs/index.rst docs/screenshots.rst docs/setup.rst docs/theme/nature/layout.html docs/usage/api_key_access.rst docs/usage/enable_git.rst docs/usage/git_support.rst production.ini requires.txt rhodecode/__init__.py rhodecode/config/deployment.ini_tmpl rhodecode/config/environment.py rhodecode/config/middleware.py rhodecode/config/routing.py rhodecode/controllers/admin/admin.py rhodecode/controllers/admin/ldap_settings.py rhodecode/controllers/admin/permissions.py rhodecode/controllers/admin/repos.py rhodecode/controllers/admin/settings.py rhodecode/controllers/admin/users.py rhodecode/controllers/api/__init__.py rhodecode/controllers/api/api.py rhodecode/controllers/branches.py rhodecode/controllers/changelog.py rhodecode/controllers/changeset.py rhodecode/controllers/error.py rhodecode/controllers/feed.py rhodecode/controllers/files.py rhodecode/controllers/home.py rhodecode/controllers/journal.py rhodecode/controllers/login.py rhodecode/controllers/search.py rhodecode/controllers/settings.py rhodecode/controllers/shortlog.py rhodecode/controllers/summary.py rhodecode/controllers/tags.py rhodecode/lib/__init__.py rhodecode/lib/auth.py rhodecode/lib/auth_ldap.py rhodecode/lib/backup_manager.py rhodecode/lib/base.py rhodecode/lib/celerylib/__init__.py rhodecode/lib/celerylib/tasks.py rhodecode/lib/db_manage.py rhodecode/lib/dbmigrate/__init__.py rhodecode/lib/dbmigrate/versions/002_version_1_1_0.py rhodecode/lib/dbmigrate/versions/__init__.py rhodecode/lib/exceptions.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/rcmail/smtp_mailer.py rhodecode/lib/smtp_mailer.py rhodecode/lib/utils.py rhodecode/model/__init__.py rhodecode/model/caching_query.py rhodecode/model/db.py rhodecode/model/forms.py rhodecode/model/permission.py rhodecode/model/repo.py rhodecode/model/repo_permission.py rhodecode/model/scm.py rhodecode/model/user.py rhodecode/public/css/diff.css rhodecode/public/css/style.css rhodecode/public/images/button_highlight_selected.png rhodecode/public/images/header_inner.png rhodecode/public/images/horizontal-indicator.png rhodecode/public/images/title.png rhodecode/public/images/title_link.png rhodecode/public/js/css_browser_selector.js rhodecode/templates/admin/permissions/permissions.html rhodecode/templates/admin/repos/repos.html rhodecode/templates/admin/settings/settings.html rhodecode/templates/admin/users/user_edit.html rhodecode/templates/admin/users/user_edit_my_account.html rhodecode/templates/base/base.html rhodecode/templates/changelog/changelog.html rhodecode/templates/changeset/changeset.html rhodecode/templates/errors/error_document.html rhodecode/templates/files/files_annotate.html rhodecode/templates/files/files_browser.html rhodecode/templates/files/files_source.html rhodecode/templates/index.html rhodecode/templates/journal/journal.html rhodecode/templates/login.html rhodecode/templates/password_reset.html rhodecode/templates/register.html rhodecode/templates/search/search_content.html rhodecode/templates/settings/repo_fork.html rhodecode/templates/summary/summary.html rhodecode/tests/__init__.py rhodecode/tests/functional/test_changelog.py rhodecode/tests/functional/test_summary.py rhodecode/tests/test_concurency.py rhodecode/websetup.py setup.cfg setup.py test.ini
diffstat 284 files changed, 25520 insertions(+), 9064 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sun Feb 19 20:21:14 2012 +0200
+++ b/.hgignore	Sun Feb 26 17:25:09 2012 +0200
@@ -1,6 +1,7 @@
 syntax: glob
 *.pyc
 *.swp
+*.sqlite
 *.egg-info
 *.egg
 
@@ -12,6 +13,9 @@
 ^\.settings$
 ^\.project$
 ^\.pydevproject$
+^\.coverage$
 ^rhodecode\.db$
 ^test\.db$
-^repositories\.config$
+^RhodeCode\.egg-info$
+^rc\.ini$
+^fabfile.py
--- a/CONTRIBUTORS	Sun Feb 19 20:21:14 2012 +0200
+++ b/CONTRIBUTORS	Sun Feb 26 17:25:09 2012 +0200
@@ -13,3 +13,6 @@
     Ankit Solanki <ankit.solanki@gmail.com>    
     Liad Shani <liadff@gmail.com>
     Les Peabody <lpeabody@gmail.com>
+    Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
+    Matt Zuba <matt.zuba@goodwillaz.org>
+    Aras Pranckevicius <aras@unity3d.com>
\ No newline at end of file
--- a/README.rst	Sun Feb 19 20:21:14 2012 +0200
+++ b/README.rst	Sun Feb 26 17:25:09 2012 +0200
@@ -1,22 +1,40 @@
-=================================================
-Welcome to RhodeCode (RhodiumCode) documentation!
-=================================================
+=========
+RhodeCode
+=========
 
-``RhodeCode`` is a Pylons framework based Mercurial repository 
-browser/management tool with a built in push/pull server and full text search.
+About
+-----
+
+``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_ 
+with a built in push/pull server and full text search and code-review.
 It works on http/https and has a built in permission/authentication system with 
-the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also supports
-simple API so it's easy integrable with existing systems.
+the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also provides
+simple API so it's easy integrable with existing external systems.
 
 RhodeCode is similar in some respects to github or bitbucket_, 
-however RhodeCode can be run as standalone hosted application on your own server.  
+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 soon GIT) repositories. 
+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 uses `Semantic Versioning <http://semver.org/>`_
 
+Installation
+------------
+Stable releases of RhodeCode are best installed via::
+
+    easy_install rhodecode
+
+Or::
+
+    pip install rhodecode 
+
+Detailed instructions and links may be found on the Installation page.
+
+Please visit http://packages.python.org/RhodeCode/installation.html for
+more details
+
 RhodeCode demo
 --------------
 
@@ -45,16 +63,11 @@
 
 https://github.com/marcinkuzminski/rhodecode
 
-Installation
-------------
-
-Please visit http://packages.python.org/RhodeCode/installation.html
-
 
 RhodeCode Features
 ------------------
 
-- Has it's own middleware to handle mercurial_ protocol requests. 
+- Has its own middleware to handle mercurial_ protocol requests. 
   Each request can be logged and authenticated.
 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
   Supports http/https and LDAP
@@ -75,6 +88,9 @@
 - Server side forks. It is possible to fork a project and modify it freely 
   without breaking the main repository. You can even write Your own hooks 
   and install them
+- code review with notification system, inline commenting, all parsed using
+  rst syntax
+- rst and markdown README support for repositories  
 - Full text search powered by Whoosh on the source files, and file names.
   Build in indexing daemons, with optional incremental index build
   (no external search servers required all in one application)
@@ -88,20 +104,14 @@
   location 
 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
 
-
-.. include:: ./docs/screenshots.rst
-    
     
 Incoming / Plans
 ----------------
 
 - Finer granular permissions per branch, repo group or subrepo
 - pull requests and web based merges
-- notification and message system 
+- per line file history
 - SSH based authentication with server side key management
-- Code review (probably based on hg-review)
-- Full git_ support, with push/pull server (currently in beta tests)
-- Redmine and other bugtrackers integration
 - Commit based built in wiki system
 - More statistics and graph (global annotation + some more statistics)
 - Other advancements as development continues (or you can of course make 
@@ -113,21 +123,35 @@
 ``RhodeCode`` is released under the GPLv3 license.
 
 
-Mailing group Q&A
------------------
+Getting help
+------------
 
-Join the `Google group <http://groups.google.com/group/rhodecode>`_
+Listed bellow are various support resources that should help.
 
-Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
+.. note::
+   
+   Please try to read the documentation before posting any issues
+ 
+- Join the `Google group <http://groups.google.com/group/rhodecode>`_ and ask
+  any questions.
 
-Join #rhodecode on FreeNode (irc.freenode.net)
-or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
+- Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
+
+
+- Join #rhodecode on FreeNode (irc.freenode.net)
+  or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
+
+- You can also follow me on twitter @marcinkuzminski where i often post some
+  news about RhodeCode
+
 
 Online documentation
 --------------------
 
 Online documentation for the current version of RhodeCode is available at
-http://packages.python.org/RhodeCode/.
+ - http://packages.python.org/RhodeCode/
+ - http://rhodecode.readthedocs.org/en/latest/index.html
+
 You may also build the documentation for yourself - go into ``docs/`` and run::
 
    make html
--- a/development.ini	Sun Feb 19 20:21:14 2012 +0200
+++ b/development.ini	Sun Feb 26 17:25:09 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 = 
@@ -32,7 +33,7 @@
 threadpool_workers = 5
 
 ##max request before thread respawn
-threadpool_max_requests = 6
+threadpool_max_requests = 10
 
 ##option to use threads of process
 use_threadpool = true
@@ -45,14 +46,52 @@
 use = egg:rhodecode
 full_stack = true
 static_files = true
-lang=en
+lang = en
 cache_dir = %(here)s/data
 index_dir = %(here)s/data/index
-app_instance_uuid = develop
+app_instance_uuid = rc-develop
 cut_off_limit = 256000
 force_https = false
 commit_parse_limit = 25
 use_gravatar = true
+container_auth_enabled = false
+proxypass_auth_enabled = false
+default_encoding = utf8
+
+## 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        ####
@@ -91,21 +130,27 @@
 
 beaker.cache.super_short_term.type=memory
 beaker.cache.super_short_term.expire=10
+beaker.cache.super_short_term.key_length = 256
 
 beaker.cache.short_term.type=memory
 beaker.cache.short_term.expire=60
+beaker.cache.short_term.key_length = 256
 
 beaker.cache.long_term.type=memory
 beaker.cache.long_term.expire=36000
+beaker.cache.long_term.key_length = 256
 
 beaker.cache.sql_cache_short.type=memory
 beaker.cache.sql_cache_short.expire=10
+beaker.cache.sql_cache_short.key_length = 256
 
 beaker.cache.sql_cache_med.type=memory
 beaker.cache.sql_cache_med.expire=360
+beaker.cache.sql_cache_med.key_length = 256
 
 beaker.cache.sql_cache_long.type=file
 beaker.cache.sql_cache_long.expire=3600
+beaker.cache.sql_cache_long.key_length = 256
 
 ####################################
 ###       BEAKER SESSION        ####
@@ -113,12 +158,26 @@
 ## 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
+#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
@@ -126,7 +185,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  ##
--- a/docs/api/api.rst	Sun Feb 19 20:21:14 2012 +0200
+++ b/docs/api/api.rst	Sun Feb 26 17:25:09 2012 +0200
@@ -26,16 +26,18 @@
 
 All clients are required to send JSON-RPC spec JSON data::
 
-    {
+    {   
+        "id:<id>,
         "api_key":"<api_key>",
         "method":"<method_name>",
         "args":{"<arg_key>":"<arg_val>"}
     }
 
 Example call for autopulling remotes repos using curl::
-    curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
+    curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
 
 Simply provide
+ - *id* A value of any type, which is used to match the response with the request that it is replying to.
  - *api_key* for access and permission validation.
  - *method* is name of method to call
  - *args* is an key:value list of arguments to pass to method
@@ -47,7 +49,8 @@
 
 RhodeCode API will return always a JSON-RPC response::
 
-    {
+    {   
+        "id":<id>,
         "result": "<result>",
         "error": null
     }
@@ -72,21 +75,55 @@
     api_key : "<api_key>"
     method :  "pull"
     args :    {
-                "repo" : "<repo_name>"
+                "repo_name" : "<reponame>"
               }
 
 OUTPUT::
 
-    result : "Pulled from <repo_name>"
+    result : "Pulled from <reponame>"
     error :  null
 
 
+get_user
+--------
+
+Get's an user by username or user_id, Returns empty result if user is not found.
+This command can be executed only using api_key belonging to user with admin 
+rights.
+
+
+INPUT::
+
+    api_key : "<api_key>"
+    method :  "get_user"
+    args :    { 
+                "userid" : "<username or user_id>"
+              }
+
+OUTPUT::
+
+    result: None if user does not exist or 
+            {
+                "id" :       "<id>",
+                "username" : "<username>",
+                "firstname": "<firstname>",
+                "lastname" : "<lastname>",
+                "email" :    "<email>",
+                "active" :   "<bool>",
+                "admin" :    "<bool>",
+                "ldap_dn" :  "<ldap_dn>"
+            }
+
+    error:  null
+
+
 get_users
 ---------
 
 Lists all existing users. This command can be executed only using api_key
 belonging to user with admin rights.
 
+
 INPUT::
 
     api_key : "<api_key>"
@@ -104,18 +141,20 @@
                 "email" :    "<email>",
                 "active" :   "<bool>",
                 "admin" :    "<bool>",
-                "ldap" :     "<ldap_dn>"
+                "ldap_dn" :  "<ldap_dn>"
               },
     	      …
             ]
     error:  null
 
+
 create_user
 -----------
 
-Creates new user or updates current one if such user exists. This command can 
+Creates new user. This command can 
 be executed only using api_key belonging to user with admin rights.
 
+
 INPUT::
 
     api_key : "<api_key>"
@@ -123,9 +162,9 @@
     args :    {
                 "username" :  "<username>",
                 "password" :  "<password>",
-                "firstname" : "<firstname>",
-                "lastname" :  "<lastname>",
-                "email" :     "<useremail>"
+                "email" :     "<useremail>",
+                "firstname" : "<firstname> = None",
+                "lastname" :  "<lastname> = None",
                 "active" :    "<bool> = True",
                 "admin" :     "<bool> = False",
                 "ldap_dn" :   "<ldap_dn> = None"
@@ -134,15 +173,88 @@
 OUTPUT::
 
     result: {
+              "id" : "<new_user_id>",
               "msg" : "created new user <username>"
             }
     error:  null
 
+
+update_user
+-----------
+
+updates current one if such user exists. This command can 
+be executed only using api_key belonging to user with admin rights.
+
+
+INPUT::
+
+    api_key : "<api_key>"
+    method :  "update_user"
+    args :    {
+                "userid" : "<user_id or username>",
+                "username" :  "<username>",
+                "password" :  "<password>",
+                "email" :     "<useremail>",
+                "firstname" : "<firstname>",
+                "lastname" :  "<lastname>",
+                "active" :    "<bool>",
+                "admin" :     "<bool>",
+                "ldap_dn" :   "<ldap_dn>"
+              }
+
+OUTPUT::
+
+    result: {
+              "id" : "<edited_user_id>",
+              "msg" : "updated user <username>"
+            }
+    error:  null
+
+
+get_users_group
+---------------
+
+Gets an existing users group. This command can be executed only using api_key
+belonging to user with admin rights.
+
+
+INPUT::
+
+    api_key : "<api_key>"
+    method :  "get_users_group"
+    args :    {
+                "group_name" : "<name>"
+              }
+
+OUTPUT::
+
+    result : None if group not exist
+             {
+               "id" :         "<id>",
+               "group_name" : "<groupname>",
+               "active":      "<bool>",
+               "members" :  [
+                              { "id" :       "<userid>",
+                                "username" : "<username>",
+                                "firstname": "<firstname>",
+                                "lastname" : "<lastname>",
+                                "email" :    "<email>",
+                                "active" :   "<bool>",
+                                "admin" :    "<bool>",
+                                "ldap" :     "<ldap_dn>"
+                              },
+                              …
+                            ]
+             }
+    error : null
+
+
 get_users_groups
 ----------------
 
-Lists all existing users groups. This command can be executed only using api_key
-belonging to user with admin rights.
+Lists all existing users groups. This command can be executed only using 
+api_key belonging to user with admin rights.
+
 
 INPUT::
 
@@ -154,9 +266,9 @@
 
     result : [
                {
-                 "id" :       "<id>",
-                 "name" :     "<name>",
-                 "active":    "<bool>",
+                 "id" :         "<id>",
+                 "group_name" : "<groupname>",
+                 "active":      "<bool>",
                  "members" :  [
 	    	                    {
 	    	                      "id" :       "<userid>",
@@ -174,41 +286,6 @@
               ]
     error : null
 
-get_users_group
----------------
-
-Gets an existing users group. This command can be executed only using api_key
-belonging to user with admin rights.
-
-INPUT::
-
-    api_key : "<api_key>"
-    method :  "get_users_group"
-    args :    {
-                "group_name" : "<name>"
-              }
-
-OUTPUT::
-
-    result : None if group not exist
-             {
-               "id" :       "<id>",
-               "name" :     "<name>",
-               "active":    "<bool>",
-               "members" :  [
-	    	                  { "id" :       "<userid>",
-	                            "username" : "<username>",
-	                            "firstname": "<firstname>",
-	                            "lastname" : "<lastname>",
-	                            "email" :    "<email>",
-	                            "active" :   "<bool>",
-	                            "admin" :    "<bool>",
-	                            "ldap" :     "<ldap_dn>"
-	                          },
-	    	                  …
-	                        ]
-             }
-    error : null
 
 create_users_group
 ------------------
@@ -216,12 +293,13 @@
 Creates new users group. This command can be executed only using api_key
 belonging to user with admin rights
 
+
 INPUT::
 
     api_key : "<api_key>"
     method :  "create_users_group"
     args:     {
-                "name":  "<name>",
+                "group_name":  "<groupname>",
                 "active":"<bool> = True"
               }
 
@@ -229,39 +307,120 @@
 
     result: {
               "id":  "<newusersgroupid>",
-              "msg": "created new users group <name>"
+              "msg": "created new users group <groupname>"
             }
     error:  null
 
+
 add_user_to_users_group
 -----------------------
 
-Adds a user to a users group. This command can be executed only using api_key
+Adds a user to a users group. If user exists in that group success will be 
+`false`. This command can be executed only using api_key
 belonging to user with admin rights
 
+
 INPUT::
 
     api_key : "<api_key>"
     method :  "add_user_users_group"
     args:     {
                 "group_name" :  "<groupname>",
-                "user_name" :   "<username>"
+                "username" :   "<username>"
               }
 
 OUTPUT::
 
     result: {
               "id":  "<newusersgroupmemberid>",
-              "msg": "created new users group member"
+              "success": True|False # depends on if member is in group
+              "msg": "added member <username> to users group <groupname> | 
+                      User is already in that group"
+            }
+    error:  null
+
+
+remove_user_from_users_group
+----------------------------
+
+Removes a user from a users group. If user is not in given group success will
+be `false`. This command can be executed only 
+using api_key belonging to user with admin rights
+
+
+INPUT::
+
+    api_key : "<api_key>"
+    method :  "remove_user_from_users_group"
+    args:     {
+                "group_name" :  "<groupname>",
+                "username" :   "<username>"
+              }
+
+OUTPUT::
+
+    result: {
+              "success":  True|False,  # depends on if member is in group
+              "msg": "removed member <username> from users group <groupname> | 
+                      User wasn't in group"
             }
     error:  null
 
+
+get_repo
+--------
+
+Gets an existing repository by it's name or repository_id. This command can 
+be executed only using api_key belonging to user with admin rights.
+
+
+INPUT::
+
+    api_key : "<api_key>"
+    method :  "get_repo"
+    args:     {
+                "repoid" : "<reponame or repo_id>"
+              }
+
+OUTPUT::
+
+    result: None if repository does not exist or
+            {
+                "id" :          "<id>",
+                "repo_name" :   "<reponame>"
+                "type" :        "<type>",
+                "description" : "<description>",
+                "members" :     [
+                                  { "id" :         "<userid>",
+                                    "username" :   "<username>",
+                                    "firstname":   "<firstname>",
+                                    "lastname" :   "<lastname>",
+                                    "email" :      "<email>",
+                                    "active" :     "<bool>",
+                                    "admin" :      "<bool>",
+                                    "ldap" :       "<ldap_dn>",
+                                    "permission" : "repository.(read|write|admin)"
+                                  },
+                                  …
+                                  {
+                                    "id" :       "<usersgroupid>",
+                                    "name" :     "<usersgroupname>",
+                                    "active":    "<bool>",
+                                    "permission" : "repository.(read|write|admin)"
+                                  },
+                                  …
+                                ]
+            }
+    error:  null
+
+
 get_repos
 ---------
 
 Lists all existing repositories. This command can be executed only using api_key
 belonging to user with admin rights
 
+
 INPUT::
 
     api_key : "<api_key>"
@@ -273,7 +432,7 @@
     result: [
               {
                 "id" :          "<id>",
-                "name" :        "<name>"
+                "repo_name" :   "<reponame>"
                 "type" :        "<type>",
                 "description" : "<description>"
               },
@@ -281,51 +440,39 @@
             ]
     error:  null
 
-get_repo
---------
+
+get_repo_nodes
+--------------
 
-Gets an existing repository. This command can be executed only using api_key
-belonging to user with admin rights
+returns a list of nodes and it's children in a flat list for a given path 
+at given revision. It's possible to specify ret_type to show only `files` or 
+`dirs`. This command can be executed only using api_key belonging to user 
+with admin rights
+
 
 INPUT::
 
     api_key : "<api_key>"
-    method :  "get_repo"
+    method :  "get_repo_nodes"
     args:     {
-                "name" : "<name>"
+                "repo_name" : "<reponame>",
+                "revision"  : "<revision>",
+                "root_path" : "<root_path>",
+                "ret_type"  : "<ret_type>" = 'all'
               }
 
 OUTPUT::
 
-    result: None if repository not exist
-            {
-                "id" :          "<id>",
+    result: [
+              {
                 "name" :        "<name>"
                 "type" :        "<type>",
-                "description" : "<description>",
-                "members" :     [
-                                  { "id" :         "<userid>",
-	                                "username" :   "<username>",
-	                                "firstname":   "<firstname>",
-	                                "lastname" :   "<lastname>",
-	                                "email" :      "<email>",
-	                                "active" :     "<bool>",
-	                                "admin" :      "<bool>",
-	                                "ldap" :       "<ldap_dn>",
-	                                "permission" : "repository.(read|write|admin)"
-	                              },
-                                  …
-                                  {
-                                    "id" :       "<usersgroupid>",
-                                    "name" :     "<usersgroupname>",
-                                    "active":    "<bool>",
-                                    "permission" : "repository.(read|write|admin)"
-                                  },
-                                  …
-                                ]
-            }
+              },
+              …
+            ]
     error:  null
 
+
 create_repo
 -----------
 
@@ -335,58 +482,146 @@
 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
 and create "baz" repository with "bar" as group.
 
+
 INPUT::
 
     api_key : "<api_key>"
     method :  "create_repo"
     args:     {
-                "name" :        "<name>",
+                "repo_name" :   "<reponame>",
                 "owner_name" :  "<ownername>",
                 "description" : "<description> = ''",
                 "repo_type" :   "<type> = 'hg'",
-                "private" :     "<bool> = False"
+                "private" :     "<bool> = False",
+                "clone_uri" :   "<clone_uri> = None",
               }
 
 OUTPUT::
 
-    result: None
+    result: {
+              "id": "<newrepoid>",
+              "msg": "Created new repository <reponame>",
+            }
     error:  null
 
-add_user_to_repo
-----------------
+
+delete_repo
+-----------
+
+Deletes a repository. This command can be executed only using api_key
+belonging to user with admin rights.
+
+
+INPUT::
+
+    api_key : "<api_key>"
+    method :  "delete_repo"
+    args:     {
+                "repo_name" :   "<reponame>",
+              }
 
-Add a user to a repository. This command can be executed only using api_key
-belonging to user with admin rights.
-If "perm" is None, user will be removed from the repository.
+OUTPUT::
+
+    result: {
+              "msg": "Deleted repository <reponame>",
+            }
+    error:  null
+
+
+grant_user_permission
+---------------------
+
+Grant permission for user on given repository, or update existing one
+if found. This command can be executed only using api_key belonging to user 
+with admin rights.
+
 
 INPUT::
 
     api_key : "<api_key>"
-    method :  "add_user_to_repo"
+    method :  "grant_user_permission"
     args:     {
                 "repo_name" :  "<reponame>",
-                "user_name" :  "<username>",
-                "perm" :       "(None|repository.(read|write|admin))",
+                "username" :   "<username>",
+                "perm" :       "(repository.(none|read|write|admin))",
+              }
+
+OUTPUT::
+
+    result: {
+              "msg" : "Granted perm: <perm> for user: <username> in repo: <reponame>"
+            }
+    error:  null
+
+
+revoke_user_permission
+----------------------
+
+Revoke permission for user on given repository. This command can be executed 
+only using api_key belonging to user with admin rights.
+
+
+INPUT::
+
+    api_key : "<api_key>"
+    method  : "revoke_user_permission"
+    args:     {
+                "repo_name" :  "<reponame>",
+                "username" :   "<username>",
               }
 
 OUTPUT::
 
-    result: None
+    result: {
+              "msg" : "Revoked perm for user: <suername> in repo: <reponame>"
+            }
     error:  null
 
-add_users_group_to_repo
------------------------
+
+grant_users_group_permission
+----------------------------
 
-Add a users group to a repository. This command can be executed only using 
-api_key belonging to user with admin rights. If "perm" is None, group will 
-be removed from the repository.
+Grant permission for users group on given repository, or update
+existing one if found. This command can be executed only using 
+api_key belonging to user with admin rights.
+
 
 INPUT::
 
     api_key : "<api_key>"
-    method :  "add_users_group_to_repo"
+    method :  "grant_users_group_permission"
+    args:     {
+                "repo_name" : "<reponame>",
+                "group_name" : "<usersgroupname>",
+                "perm" : "(repository.(none|read|write|admin))",
+              }
+
+OUTPUT::
+
+    result: {
+              "msg" : "Granted perm: <perm> for group: <usersgroupname> in repo: <reponame>"
+            }
+    error:  null
+    
+    
+revoke_users_group_permission
+-----------------------------
+
+Revoke permission for users group on given repository.This command can be 
+executed only using api_key belonging to user with admin rights.
+
+INPUT::
+
+    api_key : "<api_key>"
+    method  : "revoke_users_group_permission"
     args:     {
                 "repo_name" :  "<reponame>",
-                "group_name" :  "<groupname>",
-                "perm" :       "(None|repository.(read|write|admin))",
-              }
\ No newline at end of file
+                "users_group" :   "<usersgroupname>",
+              }
+
+OUTPUT::
+
+    result: {
+              "msg" : "Revoked perm for group: <usersgroupname> in repo: <reponame>"
+            }
+    error:  null
\ No newline at end of file
--- a/docs/api/index.rst	Sun Feb 19 20:21:14 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-.. _indexapi:
-
-API Reference
-=============
-
-.. toctree::
-   :maxdepth: 3
-
-   models
-   api 
\ No newline at end of file
--- a/docs/api/models.rst	Sun Feb 19 20:21:14 2012 +0200
+++ b/docs/api/models.rst	Sun Feb 26 17:25:09 2012 +0200
@@ -6,14 +6,29 @@
 .. automodule:: rhodecode.model
    :members:
    
+.. automodule:: rhodecode.model.comment
+   :members:
+  
+.. automodule:: rhodecode.model.notification
+   :members:   
+
 .. automodule:: rhodecode.model.permission
    :members:
-  
+
+.. automodule:: rhodecode.model.repo_permission
+   :members:      
+
 .. automodule:: rhodecode.model.repo
    :members:   
 
+.. automodule:: rhodecode.model.repos_group
+   :members:
+   
 .. automodule:: rhodecode.model.scm
    :members:
-
+   
 .. automodule:: rhodecode.model.user
    :members:      
+   
+.. automodule:: rhodecode.model.users_group
+   :members:   
\ No newline at end of file
--- a/docs/changelog.rst	Sun Feb 19 20:21:14 2012 +0200
+++ b/docs/changelog.rst	Sun Feb 26 17:25:09 2012 +0200
@@ -4,14 +4,73 @@
 =========
 
 
-1.2.5 (**2012-01-28**)
-======================
+1.3.0 (**2012-02-XX**)
+----------------------
+
+:status: in-progress
+:branch: beta
 
 news
-----
+++++
+
+- code review, inspired by github code-comments 
+- #215 rst and markdown README files support
+- #252 Container-based and proxy pass-through authentication support
+- #44 branch browser. Filtering of changelog by branches
+- mercurial bookmarks support
+- new hover top menu, optimized to add maximum size for important views
+- configurable clone url template with possibility to specify  protocol like 
+  ssh:// or http:// and also manually alter other parts of clone_url.
+- enabled largefiles extension by default
+- optimized summary file pages and saved a lot of unused space in them
+- #239 option to manually mark repository as fork
+- #320 mapping of commit authors to RhodeCode users
+- #304 hashes are displayed using monospace font    
+- diff configuration, toggle white lines and context lines
+- #307 configurable diffs, whitespace toggle, increasing context lines
+- sorting on branches, tags and bookmarks using YUI datatable
+- improved file filter on files page
+- implements #330 api method for listing nodes ar particular revision
+- #73 added linking issues in commit messages to chosen issue tracker url
+  based on user defined regular expression
+- added linking of changesets in commit messages  
+- new compact changelog with expandable commit messages
+- firstname and lastname are optional in user creation
+- #348 added post-create repository hook
+- #212 global encoding settings is now configurable from .ini files 
+- #227 added repository groups permissions
+- markdown gets codehilite extensions
+- new API methods, delete_repositories, grante/revoke permissions for groups 
+  and repos
+  
+    
+fixes
++++++
+
+- rewrote dbsession management for atomic operations, and better error handling
+- fixed sorting of repo tables
+- #326 escape of special html entities in diffs
+- normalized user_name => username in api attributes
+- fixes #298 ldap created users with mixed case emails created conflicts 
+  on saving a form
+- fixes issue when owner of a repo couldn't revoke permissions for users 
+  and groups
+- fixes #271 rare JSON serialization problem with statistics
+- fixes #337 missing validation check for conflicting names of a group with a
+  repositories group
+- #340 fixed session problem for mysql and celery tasks
+- fixed #331 RhodeCode mangles repository names if the a repository group 
+  contains the "full path" to the repositories
+- #355 RhodeCode doesn't store encrypted LDAP passwords
+
+1.2.5 (**2012-01-28**)
+----------------------
+
+news
+++++
 
 fixes
------
++++++
 
 - #340 Celery complains about MySQL server gone away, added session cleanup
   for celery tasks
@@ -24,10 +83,10 @@
   forking on windows impossible 
 
 1.2.4 (**2012-01-19**)
-======================
+----------------------
 
 news
-----
+++++
 
 - RhodeCode is bundled with mercurial series 2.0.X by default, with
   full support to largefiles extension. Enabled by default in new installations
@@ -35,7 +94,7 @@
 - added requires.txt file with requirements
      
 fixes
------
++++++
 
 - fixes db session issues with celery when emailing admins
 - #331 RhodeCode mangles repository names if the a repository group 
@@ -52,10 +111,10 @@
 - #316 fixes issues with web description in hgrc files 
 
 1.2.3 (**2011-11-02**)
-======================
+----------------------
 
 news
-----
+++++
 
 - added option to manage repos group for non admin users
 - added following API methods for get_users, create_user, get_users_groups, 
@@ -67,24 +126,23 @@
   administrator users, and global config email.
      
 fixes
------
++++++
 
 - added option for passing auth method for smtp mailer
 - #276 issue with adding a single user with id>10 to usergroups
 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth 
 - #288 fixes managing of repos in a group for non admin user
 
-
 1.2.2 (**2011-10-17**)
-======================
+----------------------
 
 news
-----
+++++
 
 - #226 repo groups are available by path instead of numerical id
  
 fixes
------
++++++
 
 - #259 Groups with the same name but with different parent group
 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
@@ -98,27 +156,25 @@
 - fixes #248 cannot edit repos inside a group on windows
 - fixes #219 forking problems on windows
 
-
 1.2.1 (**2011-10-08**)
-======================
+----------------------
 
 news
-----
+++++
 
 
 fixes
------
++++++
 
 - fixed problems with basic auth and push problems 
 - gui fixes
 - fixed logger
 
-
 1.2.0 (**2011-10-07**)
-======================
+----------------------
 
 news
-----
+++++
 
 - implemented #47 repository groups
 - implemented #89 Can setup google analytics code from settings menu
@@ -158,7 +214,7 @@
 - Implemented advanced hook management
 
 fixes
------
++++++
 
 - fixed file browser bug, when switching into given form revision the url was 
   not changing
@@ -185,18 +241,17 @@
 - fixes #218 os.kill patch for windows was missing sig param
 - improved rendering of dag (they are not trimmed anymore when number of 
   heads exceeds 5)
-
-
+    
 1.1.8 (**2011-04-12**)
-======================
+----------------------
 
 news
-----
+++++
 
 - improved windows support
 
 fixes
------
++++++
 
 - fixed #140 freeze of python dateutil library, since new version is python2.x
   incompatible
@@ -219,40 +274,40 @@
 
 
 1.1.7 (**2011-03-23**)
-======================
+----------------------
 
 news
-----
+++++
 
 fixes
------
++++++
 
 - fixed (again) #136 installation support for FreeBSD
 
 
 1.1.6 (**2011-03-21**)
-======================
+----------------------
 
 news
-----
+++++
 
 fixes
------
++++++
 
 - fixed #136 installation support for FreeBSD
 - RhodeCode will check for python version during installation
 
 1.1.5 (**2011-03-17**)
-======================
+----------------------
 
 news
-----
+++++
 
 - basic windows support, by exchanging pybcrypt into sha256 for windows only
   highly inspired by idea of mantis406
 
 fixes
------
++++++
 
 - fixed sorting by author in main page
 - fixed crashes with diffs on binary files
@@ -264,13 +319,13 @@
 - cleaned out docs, big thanks to Jason Harris
 
 1.1.4 (**2011-02-19**)
-======================
+----------------------
 
 news
-----
+++++
 
 fixes
------
++++++
 
 - fixed formencode import problem on settings page, that caused server crash
   when that page was accessed as first after server start
@@ -278,17 +333,17 @@
 - fixed option to access repository just by entering http://server/<repo_name> 
 
 1.1.3 (**2011-02-16**)
-======================
+----------------------
 
 news
-----
+++++
 
 - implemented #102 allowing the '.' character in username
 - added option to access repository just by entering http://server/<repo_name>
 - celery task ignores result for better performance
 
 fixes
------
++++++
 
 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to 
   apollo13 and Johan Walles
@@ -304,31 +359,31 @@
 - fixed static files paths links to use of url() method
 
 1.1.2 (**2011-01-12**)
-======================
+----------------------
 
 news
-----
+++++
 
 
 fixes
------
++++++
 
 - fixes #98 protection against float division of percentage stats
 - fixed graph bug
 - forced webhelpers version since it was making troubles during installation 
 
 1.1.1 (**2011-01-06**)
-======================
+----------------------
  
 news
-----
+++++
 
 - added force https option into ini files for easier https usage (no need to
   set server headers with this options)
 - small css updates
 
 fixes
------
++++++
 
 - fixed #96 redirect loop on files view on repositories without changesets
 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
@@ -337,16 +392,16 @@
 - fixed #92 whoosh indexer is more error proof
 
 1.1.0 (**2010-12-18**)
-======================
+----------------------
 
 news
-----
+++++
 
 - rewrite of internals for vcs >=0.1.10
 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility 
   with older clients
 - anonymous access, authentication via ldap
-- performance upgrade for cached repos list - each repository has it's own 
+- performance upgrade for cached repos list - each repository has its own 
   cache that's invalidated when needed.
 - performance upgrades on repositories with large amount of commits (20K+)
 - main page quick filter for filtering repositories
@@ -365,7 +420,7 @@
 - other than sqlite database backends can be used
 
 fixes
------
++++++
 
 - fixes #61 forked repo was showing only after cache expired
 - fixes #76 no confirmation on user deletes
@@ -385,16 +440,16 @@
 
 
 1.0.2 (**2010-11-12**)
-======================
+----------------------
 
 news
-----
+++++
 
 - tested under python2.7
 - bumped sqlalchemy and celery versions
 
 fixes
------
++++++
 
 - fixed #59 missing graph.js
 - fixed repo_size crash when repository had broken symlinks
@@ -402,15 +457,15 @@
 
 
 1.0.1 (**2010-11-10**)
-======================
+----------------------
 
 news
-----
+++++
 
 - small css updated
 
 fixes
------
++++++
 
 - fixed #53 python2.5 incompatible enumerate calls
 - fixed #52 disable mercurial extension for web
@@ -418,7 +473,7 @@
 
 
 1.0.0 (**2010-11-02**)
-======================
+----------------------
 
 - security bugfix simplehg wasn't checking for permissions on commands
   other than pull or push.
@@ -428,7 +483,7 @@
 - permissions cached queries
 
 1.0.0rc4  (**2010-10-12**)
-==========================
+--------------------------
 
 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
 - removed cache_manager settings from sqlalchemy meta
@@ -438,12 +493,12 @@
 
 
 1.0.0rc3 (**2010-10-11**)
-=========================
+-------------------------
 
 - fixed i18n during installation.
 
 1.0.0rc2 (**2010-10-11**)
-=========================
+-------------------------
 
 - Disabled dirsize in file browser, it's causing nasty bug when dir renames 
   occure. After vcs is fixed it'll be put back again.
Binary file docs/images/screenshot1_main_page.png has changed
Binary file docs/images/screenshot2_summary_page.png has changed
Binary file docs/images/screenshot3_changelog_page.png has changed
--- a/docs/index.rst	Sun Feb 19 20:21:14 2012 +0200
+++ b/docs/index.rst	Sun Feb 26 17:25:09 2012 +0200
@@ -2,8 +2,8 @@
 
 .. include:: ./../README.rst
 
-Documentation
--------------
+Users Guide
+-----------
 
 **Installation:**
 
@@ -20,10 +20,9 @@
    :maxdepth: 1
 
    usage/general
-   usage/enable_git
+   usage/git_support
    usage/statistics
    usage/backup
-   usage/api_key_access
    
 **Develop**
 
@@ -36,9 +35,10 @@
 **API**
 
 .. toctree::
-   :maxdepth: 2
+   :maxdepth: 1
 
-   api/index
+   api/api
+   api/models
    
 
 Other topics
--- a/docs/screenshots.rst	Sun Feb 19 20:21:14 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-.. _screenshots:
-
-.. figure::  images/screenshot1_main_page.png
-
-   Main page of RhodeCode
-
-.. figure::  images/screenshot2_summary_page.png
-
-   Summary page
-   
-.. figure::  images/screenshot3_changelog_page.png
-
-   Changelog with DAG graph
\ No newline at end of file
--- a/docs/setup.rst	Sun Feb 19 20:21:14 2012 +0200
+++ b/docs/setup.rst	Sun Feb 26 17:25:09 2012 +0200
@@ -346,6 +346,106 @@
 appropriately configured.
 
 
+Authentication by container or reverse-proxy
+--------------------------------------------
+
+Starting with version 1.3, RhodeCode supports delegating the authentication
+of users to its WSGI container, or to a reverse-proxy server through which all
+clients access the application.
+
+When these authentication methods are enabled in RhodeCode, it uses the
+username that the container/proxy (Apache/Nginx/etc) authenticated and doesn't
+perform the authentication itself. The authorization, however, is still done by
+RhodeCode according to its settings.
+
+When a user logs in for the first time using these authentication methods,
+a matching user account is created in RhodeCode with default permissions. An
+administrator can then modify it using RhodeCode's admin interface.
+It's also possible for an administrator to create accounts and configure their
+permissions before the user logs in for the first time.
+
+Container-based authentication
+''''''''''''''''''''''''''''''
+
+In a container-based authentication setup, RhodeCode reads the user name from
+the ``REMOTE_USER`` server variable provided by the WSGI container.
+
+After setting up your container (see `Apache's WSGI config`_), you'd need
+to configure it to require authentication on the location configured for
+RhodeCode.
+
+In order for RhodeCode to start using the provided username, you should set the
+following in the [app:main] section of your .ini file::
+
+    container_auth_enabled = true
+
+
+Proxy pass-through authentication
+'''''''''''''''''''''''''''''''''
+
+In a proxy pass-through authentication setup, RhodeCode reads the user name
+from the ``X-Forwarded-User`` request header, which should be configured to be
+sent by the reverse-proxy server.
+
+After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
+`Apache as subdirectory`_ or `Nginx virtual host example`_), you'd need to
+configure the authentication and add the username in a request header named
+``X-Forwarded-User``.
+
+For example, the following config section for Apache sets a subdirectory in a
+reverse-proxy setup with basic auth::
+
+    <Location /<someprefix> >
+      ProxyPass http://127.0.0.1:5000/<someprefix>
+      ProxyPassReverse http://127.0.0.1:5000/<someprefix>
+      SetEnvIf X-Url-Scheme https HTTPS=1
+
+      AuthType Basic
+      AuthName "RhodeCode authentication"
+      AuthUserFile /home/web/rhodecode/.htpasswd
+      require valid-user
+
+      RequestHeader unset X-Forwarded-User
+
+      RewriteEngine On
+      RewriteCond %{LA-U:REMOTE_USER} (.+)
+      RewriteRule .* - [E=RU:%1]
+      RequestHeader set X-Forwarded-User %{RU}e
+    </Location> 
+
+In order for RhodeCode to start using the forwarded username, you should set
+the following in the [app:main] section of your .ini file::
+
+    proxypass_auth_enabled = true
+
+.. note::
+   If you enable proxy pass-through authentication, make sure your server is
+   only accessible through the proxy. Otherwise, any client would be able to
+   forge the authentication header and could effectively become authenticated
+   using any account of their liking.
+
+Integration with Issue trackers
+-------------------------------
+
+RhodeCode provides a simple integration with issue trackers. It's possible
+to define a regular expression that will fetch issue id stored in commit
+messages and replace that with an url to this issue. To enable this simply
+uncomment following variables in the ini file::
+
+    url_pat = (?:^#|\s#)(\w+)
+    issue_server_link = https://myissueserver.com/{repo}/issue/{id}
+    issue_prefix = #
+
+`url_pat` is the regular expression that will fetch issues from commit messages.
+Default regex will match issues in format of #<number> eg. #300.
+ 
+Matched issues will be replace with the link specified as `issue_server_link` 
+{id} will be replaced with issue id, and {repo} with repository name.
+Since the # is striped `issue_prefix` is added as a prefix to url. 
+`issue_prefix` can be something different than # if you pass 
+ISSUE- as issue prefix this will generate an url in format::
+ 
+  <a href="https://myissueserver.com/example_repo/issue/300">ISSUE-300</a>  
 
 Hook management
 ---------------
@@ -361,6 +461,17 @@
 can be found at *rhodecode.lib.hooks*. 
 
 
+Changing default encoding
+-------------------------
+
+By default RhodeCode uses utf8 encoding, starting from 1.3 series this
+can be changed, simply edit default_encoding in .ini file to desired one.
+This affects many parts in rhodecode including commiters names, filenames,
+encoding of commit messages. In addition RhodeCode can detect if `chardet`
+library is installed. If `chardet` is detected RhodeCode will fallback to it
+when there are encode/decode errors.
+
+
 Setting Up Celery
 -----------------
 
@@ -397,27 +508,36 @@
 
 Sample config for nginx using proxy::
 
+    upstream rc {
+        server 127.0.0.1:5000;
+        # add more instances for load balancing
+        #server 127.0.0.1:5001;
+        #server 127.0.0.1:5002;
+    }
+    
     server {
        listen          80;
        server_name     hg.myserver.com;
        access_log      /var/log/nginx/rhodecode.access.log;
        error_log       /var/log/nginx/rhodecode.error.log;
+
        location / {
-               root /var/www/rhodecode/rhodecode/public/;
-               if (!-f $request_filename){
-                   proxy_pass      http://127.0.0.1:5000;
-               }
-               #this is important if you want to use https !!!
-               proxy_set_header X-Url-Scheme $scheme;
-               include         /etc/nginx/proxy.conf;  
+            try_files $uri @rhode;
        }
+    
+       location @rhode {
+            proxy_pass      http://rc;
+            include         /etc/nginx/proxy.conf;
+       }
+
     }  
   
 Here's the proxy.conf. It's tuned so it will not timeout on long
 pushes or large pushes::
-
+    
     proxy_redirect              off;
     proxy_set_header            Host $host;
+    proxy_set_header            X-Url-Scheme $scheme;
     proxy_set_header            X-Host $http_host;
     proxy_set_header            X-Real-IP $remote_addr;
     proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;
--- a/docs/theme/nature/layout.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/docs/theme/nature/layout.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,12 +1,12 @@
 {% extends "basic/layout.html" %}
 
 {% block sidebarlogo %}
-<h3>Support my development effort.</h3>
+<h3>Support RhodeCode development.</h3>
 <div style="text-align:center">
 	<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
 	<input type="hidden" name="cmd" value="_s-xclick">
 	<input type="hidden" name="hosted_button_id" value="8U2LLRPLBKWDU">
-	<input style="border:0px !important" type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif" 
+	<input style="border:0px !important" type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif"
 	border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
 	<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 	</form>
--- a/docs/usage/api_key_access.rst	Sun Feb 19 20:21:14 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-.. _api_key_access:
-
-Access to RhodeCode via API KEY
-===============================
-
-Starting from version 1.2 rss/atom feeds and journal feeds
-can be accessed via **api_key**. This unique key is automatically generated for
-each user in RhodeCode application. Using this key it is possible to access 
-feeds without having to log in. When user changes his password a new API KEY
-is generated for him automatically. You can check your API KEY in account 
-settings page. 
\ No newline at end of file
--- a/docs/usage/enable_git.rst	Sun Feb 19 20:21:14 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-.. _enable_git:
-
-Enabling GIT support (beta)
-===========================
-
-
-Git support in RhodeCode 1.1 was disabled due to current instability issues. 
-However,if you would like to test git support please feel free to re-enable it. 
-To re-enable GIT support just uncomment the git line in the 
-file **rhodecode/__init__.py**
-
-.. code-block:: python
- 
-   BACKENDS = {
-       'hg': 'Mercurial repository',
-       #'git': 'Git repository',
-   }
-
-.. note::
-   Please note that the git support provided by RhodeCode is not yet fully
-   stable and RhodeCode might crash while using git repositories. (That is why
-   it is currently disabled.) Thus be careful about enabling git support, and
-   certainly don't use it in a production setting!
-   
\ No newline at end of file
--- a/docs/usage/general.rst	Sun Feb 19 20:21:14 2012 +0200
+++ b/docs/usage/general.rst	Sun Feb 26 17:25:09 2012 +0200
@@ -36,6 +36,31 @@
 one changeset
 
 
+Non changeable repository urls
+------------------------------
+
+Due to complicated nature of repository grouping, often urls of repositories
+can change.
+
+example::
+  
+  #before
+  http://server.com/repo_name
+  # after insertion to test_group group the url will be
+  http://server.com/test_group/repo_name
+  
+This can be an issue for build systems and any other hardcoded scripts, moving
+repository to a group leads to a need for changing external systems. To 
+overcome this RhodeCode introduces a non changable replacement url. It's 
+simply an repository ID prefixed with `_` above urls are also accessible as::
+
+  http://server.com/_<ID>
+  
+Since ID are always the same moving the repository will not affect such url.
+the _<ID> syntax can be used anywhere in the system so urls with repo_name 
+for changelogs, files and other can be exchanged with _<ID> syntax.
+
+
 
 Mailing
 -------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/usage/git_support.rst	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,48 @@
+.. _git_support:
+
+GIT support
+===========
+
+
+Git support in RhodeCode 1.3 was enabled by default. 
+Although There are some limitations on git usage.
+
+- No hooks are runned for git push/pull actions.
+- logs in action journals don't have git operations
+- large pushes needs http server with chunked encoding support.
+ 
+if you plan to use git you need to run RhodeCode with some
+http server that supports chunked encoding which git http protocol uses, 
+i recommend using waitress_ or gunicorn_ (linux only) for `paste` wsgi app 
+replacement.
+
+To use waitress simply change change the following in the .ini file::
+
+    use = egg:Paste#http
+
+To::
+    
+    use = egg:waitress#main
+
+And comment out bellow options::
+
+    threadpool_workers = 
+    threadpool_max_requests = 
+    use_threadpool = 
+    
+
+You can simply run `paster serve` as usual.
+
+  
+You can always disable git/hg support by editing a 
+file **rhodecode/__init__.py** and commenting out backends
+
+.. code-block:: python
+ 
+   BACKENDS = {
+       'hg': 'Mercurial repository',
+       #'git': 'Git repository',
+   }
+
+.. _waitress: http://pypi.python.org/pypi/waitress
+.. _gunicorn: http://pypi.python.org/pypi/gunicorn
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/init.d/celeryd-upstart.conf	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,34 @@
+# celeryd - run the celeryd daemon as an upstart job for rhodecode
+# Change variables/paths as necessary and place file /etc/init/celeryd.conf
+# start/stop/restart as normal upstart job (ie: $ start celeryd)
+
+description	"Celery for RhodeCode Mercurial Server"
+author		"Matt Zuba <matt.zuba@goodwillaz.org"
+
+start on starting rhodecode
+stop on stopped rhodecode
+
+respawn
+
+umask 0022
+
+env PIDFILE=/tmp/celeryd.pid
+env APPINI=/var/hg/rhodecode/production.ini
+env HOME=/var/hg
+env USER=hg
+# To use group (if different from user), you must edit sudoers file and change
+# root's entry from (ALL) to (ALL:ALL)
+# env GROUP=hg
+
+script
+    COMMAND="/var/hg/.virtualenvs/rhodecode/bin/paster celeryd $APPINI --pidfile=$PIDFILE"
+    if [ -z "$GROUP" ]; then
+        exec sudo -u $USER $COMMAND
+    else
+        exec sudo -u $USER -g $GROUP $COMMAND
+    fi
+end script
+
+post-stop script
+    rm -f $PIDFILE
+end script
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/init.d/rhodecode-upstart.conf	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,26 @@
+# rhodecode - run the rhodecode daemon as an upstart job
+# Change variables/paths as necessary and place file /etc/init/rhodecode.conf
+# start/stop/restart as normal upstart job (ie: $ start rhodecode)
+
+description	"RhodeCode Mercurial Server"
+author		"Matt Zuba <matt.zuba@goodwillaz.org"
+
+start on (local-filesystems and runlevel [2345])
+stop on runlevel [!2345]
+
+respawn
+
+umask 0022
+
+env PIDFILE=/var/hg/rhodecode/rhodecode.pid
+env LOGFILE=/var/hg/rhodecode/log/rhodecode.log
+env APPINI=/var/hg/rhodecode/production.ini
+env HOME=/var/hg
+env USER=hg
+env GROUP=hg
+
+exec /var/hg/.virtualenvs/rhodecode/bin/paster serve --user=$USER --group=$GROUP --pid-file=$PIDFILE --log-file=$LOGFILE $APPINI
+
+post-stop script
+	rm -f $PIDFILE
+end script
--- a/production.ini	Sun Feb 19 20:21:14 2012 +0200
+++ b/production.ini	Sun Feb 26 17:25:09 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 = 
@@ -45,14 +46,52 @@
 use = egg:rhodecode
 full_stack = true
 static_files = true
-lang=en
+lang = en
 cache_dir = %(here)s/data
 index_dir = %(here)s/data/index
-app_instance_uuid = prod1234
+app_instance_uuid = rc-production
 cut_off_limit = 256000
-force_https = false 
+force_https = false
 commit_parse_limit = 50
 use_gravatar = true
+container_auth_enabled = false
+proxypass_auth_enabled = false
+default_encoding = utf8
+
+## 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        ####
@@ -91,21 +130,27 @@
 
 beaker.cache.super_short_term.type=memory
 beaker.cache.super_short_term.expire=10
+beaker.cache.super_short_term.key_length = 256
 
 beaker.cache.short_term.type=memory
 beaker.cache.short_term.expire=60
+beaker.cache.short_term.key_length = 256
 
 beaker.cache.long_term.type=memory
 beaker.cache.long_term.expire=36000
+beaker.cache.long_term.key_length = 256
 
 beaker.cache.sql_cache_short.type=memory
 beaker.cache.sql_cache_short.expire=10
+beaker.cache.sql_cache_short.key_length = 256
 
 beaker.cache.sql_cache_med.type=memory
 beaker.cache.sql_cache_med.expire=360
+beaker.cache.sql_cache_med.key_length = 256
 
 beaker.cache.sql_cache_long.type=file
 beaker.cache.sql_cache_long.expire=3600
+beaker.cache.sql_cache_long.key_length = 256
 
 ####################################
 ###       BEAKER SESSION        ####
@@ -113,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
@@ -126,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  ##
@@ -232,4 +292,4 @@
 [formatter_color_formatter_sql]
 class=rhodecode.lib.colored_formatter.ColorFormatterSql
 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S
\ No newline at end of file
+datefmt = %Y-%m-%d %H:%M:%S
--- a/requires.txt	Sun Feb 19 20:21:14 2012 +0200
+++ b/requires.txt	Sun Feb 26 17:25:09 2012 +0200
@@ -1,16 +1,17 @@
 Pylons==1.0.0
-Beaker==1.5.4
+Beaker==1.6.2
 WebHelpers>=1.2
 formencode==1.2.4
 SQLAlchemy==0.7.4
 Mako==0.5.0
 pygments>=1.4
-whoosh<1.8
+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
-vcs==0.2.2
 webob==1.0.8
+markdown==2.1.1
+docutils==0.8.1
 py-bcrypt
-mercurial==2.0.2
\ No newline at end of file
+mercurial>=2.1,<2.2
\ No newline at end of file
--- a/rhodecode/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -26,9 +26,9 @@
 import sys
 import platform
 
-VERSION = (1, 2, 5)
+VERSION = (1, 3, 0)
 __version__ = '.'.join((str(each) for each in VERSION[:4]))
-__dbversion__ = 3  # defines current db version for migrations
+__dbversion__ = 5  # defines current db version for migrations
 __platform__ = platform.system()
 __license__ = 'GPLv3'
 __py_version__ = sys.version_info
@@ -38,19 +38,20 @@
 
 requirements = [
     "Pylons==1.0.0",
-    "Beaker==1.5.4",
+    "Beaker==1.6.2",
     "WebHelpers>=1.2",
     "formencode==1.2.4",
     "SQLAlchemy==0.7.4",
     "Mako==0.5.0",
     "pygments>=1.4",
-    "whoosh<1.8",
+    "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",
-    "vcs==0.2.2",
-    "webob==1.0.8"
+    "webob==1.0.8",
+    "markdown==2.1.1",
+    "docutils==0.8.1",
 ]
 
 if __py_version__ < (2, 6):
@@ -58,15 +59,15 @@
     requirements.append("pysqlite")
 
 if __platform__ in PLATFORM_WIN:
-    requirements.append("mercurial==2.0.1")
+    requirements.append("mercurial>=2.1,<2.2")
 else:
     requirements.append("py-bcrypt")
-    requirements.append("mercurial==2.0.2")
+    requirements.append("mercurial>=2.1,<2.2")
 
 
 try:
     from rhodecode.lib import get_current_revision
-    _rev = get_current_revision(quiet=True)
+    _rev = get_current_revision()
 except ImportError:
     # this is needed when doing some setup.py operations
     _rev = False
@@ -82,5 +83,10 @@
 
 BACKENDS = {
     'hg': 'Mercurial repository',
-    #'git': 'Git repository',
+    'git': 'Git repository',
 }
+
+CELERY_ON = False
+
+# link to config for pylons
+CONFIG = {}
--- a/rhodecode/config/deployment.ini_tmpl	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/config/deployment.ini_tmpl	Sun Feb 26 17:25:09 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 = 
@@ -45,14 +46,52 @@
 use = egg:rhodecode
 full_stack = true
 static_files = true
-lang=en
+lang = en
 cache_dir = %(here)s/data
 index_dir = %(here)s/data/index
 app_instance_uuid = ${app_instance_uuid}
 cut_off_limit = 256000
-force_https = false 
+force_https = false
 commit_parse_limit = 50
 use_gravatar = true
+container_auth_enabled = false
+proxypass_auth_enabled = false
+default_encoding = utf8
+
+## 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        ####
@@ -91,21 +130,27 @@
 
 beaker.cache.super_short_term.type=memory
 beaker.cache.super_short_term.expire=10
+beaker.cache.super_short_term.key_length = 256
 
 beaker.cache.short_term.type=memory
 beaker.cache.short_term.expire=60
+beaker.cache.short_term.key_length = 256
 
 beaker.cache.long_term.type=memory
 beaker.cache.long_term.expire=36000
+beaker.cache.long_term.key_length = 256
 
 beaker.cache.sql_cache_short.type=memory
 beaker.cache.sql_cache_short.expire=10
+beaker.cache.sql_cache_short.key_length = 256
 
 beaker.cache.sql_cache_med.type=memory
 beaker.cache.sql_cache_med.expire=360
+beaker.cache.sql_cache_med.key_length = 256
 
 beaker.cache.sql_cache_long.type=file
 beaker.cache.sql_cache_long.expire=3600
+beaker.cache.sql_cache_long.key_length = 256
 
 ####################################
 ###       BEAKER SESSION        ####
@@ -113,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 = ${app_instance_secret}
+# secure cookie requires AES python libraries
+#beaker.session.encrypt_key = ${app_instance_secret}
+#beaker.session.validate_key = ${app_instance_secret}
 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
@@ -126,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  ##
@@ -154,6 +214,7 @@
 # MySQL
 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
 
+# see sqlalchemy docs for others
 
 sqlalchemy.db1.echo = false
 sqlalchemy.db1.pool_recycle = 3600
@@ -217,13 +278,13 @@
 class = StreamHandler
 args = (sys.stderr,)
 level = INFO
-formatter = color_formatter
+formatter = generic
 
 [handler_console_sql]
 class = StreamHandler
 args = (sys.stderr,)
 level = WARN
-formatter = color_formatter_sql
+formatter = generic
 
 ################
 ## FORMATTERS ##
@@ -241,4 +302,4 @@
 [formatter_color_formatter_sql]
 class=rhodecode.lib.colored_formatter.ColorFormatterSql
 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S
\ No newline at end of file
+datefmt = %Y-%m-%d %H:%M:%S
--- a/rhodecode/config/environment.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/config/environment.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,13 +7,14 @@
 from pylons.configuration import PylonsConfig
 from pylons.error import handle_mako_error
 
+import rhodecode
 import rhodecode.lib.app_globals as app_globals
 import rhodecode.lib.helpers
 
 from rhodecode.config.routing import make_map
-from rhodecode.lib import celerypylons
+# 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.timerproxy import TimerProxy
 from rhodecode.lib.auth import set_available_permissions
 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
 from rhodecode.model import init_model
@@ -38,10 +39,13 @@
     # Initialize config with the basic options
     config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
 
+    # store some globals into rhodecode
+    rhodecode.CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
+
     config['routes.map'] = make_map(config)
     config['pylons.app_globals'] = app_globals.Globals(config)
     config['pylons.h'] = rhodecode.lib.helpers
-
+    rhodecode.CONFIG = config
     # Setup cache object as early as possible
     import pylons
     pylons.cache._push_object(config['pylons.app_globals'].cache)
@@ -54,7 +58,7 @@
         input_encoding='utf-8', default_filters=['escape'],
         imports=['from webhelpers.html import escape'])
 
-    #sets the c attribute access when don't existing attribute are accessed
+    # sets the c attribute access when don't existing attribute are accessed
     config['pylons.strict_tmpl_context'] = True
     test = os.path.split(config['__file__'])[-1] == 'test.ini'
     if test:
@@ -63,7 +67,7 @@
         create_test_env(TESTS_TMP_PATH, config)
         create_test_index(TESTS_TMP_PATH, config, True)
 
-    #MULTIPLE DB configs
+    # MULTIPLE DB configs
     # Setup the SQLAlchemy database engine
     sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
 
@@ -77,4 +81,7 @@
     # CONFIGURATION OPTIONS HERE (note: all config options will override
     # any Pylons config options)
 
+    # store config reference into our module to skip import magic of
+    # pylons
+    rhodecode.CONFIG.update(config)
     return config
--- a/rhodecode/config/middleware.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/config/middleware.py	Sun Feb 26 17:25:09 2012 +0200
@@ -51,15 +51,16 @@
         from rhodecode.lib.profiler import ProfilingMiddleware
         app = ProfilingMiddleware(app)
 
-    # we want our low level middleware to get to the request ASAP. We don't
-    # need any pylons stack middleware in them
-    app = SimpleHg(app, config)
-    app = SimpleGit(app, config)
+    if asbool(full_stack):
 
-    if asbool(full_stack):
         # Handle Python exceptions
         app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
 
+        # we want our low level middleware to get to the request ASAP. We don't
+        # need any pylons stack middleware in them
+        app = SimpleHg(app, config)
+        app = SimpleGit(app, config)
+
         # Display error documents for 401, 403, 404 status codes (and
         # 500 when debug is disabled)
         if asbool(config['debug']):
--- a/rhodecode/config/routing.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/config/routing.py	Sun Feb 26 17:25:09 2012 +0200
@@ -8,7 +8,6 @@
 from __future__ import with_statement
 from routes import Mapper
 
-
 # prefix for non repository related links needs to be prefixed with `/`
 ADMIN_PREFIX = '/_admin'
 
@@ -26,18 +25,27 @@
     def check_repo(environ, match_dict):
         """
         check for valid repository for proper 404 handling
-        
+
         :param environ:
         :param match_dict:
         """
+        from rhodecode.model.db import Repository
+        repo_name = match_dict.get('repo_name')
 
-        repo_name = match_dict.get('repo_name')
+        try:
+            by_id = repo_name.split('_')
+            if len(by_id) == 2 and by_id[1].isdigit():
+                repo_name = Repository.get(by_id[1]).repo_name
+                match_dict['repo_name'] = repo_name
+        except:
+            pass
+
         return is_valid_repo(repo_name, config['base_path'])
 
     def check_group(environ, match_dict):
         """
         check for valid repositories group for proper 404 handling
-        
+
         :param environ:
         :param match_dict:
         """
@@ -45,7 +53,6 @@
 
         return is_valid_repos_group(repos_group_name, config['base_path'])
 
-
     def check_int(environ, match_dict):
         return match_dict.get('id').isdigit()
 
@@ -62,9 +69,14 @@
     rmap.connect('home', '/', controller='home', action='index')
     rmap.connect('repo_switcher', '/repos', controller='home',
                  action='repo_switcher')
+    rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}',
+                 controller='home', action='branch_tag_switcher')
     rmap.connect('bugtracker',
                  "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
                  _static=True)
+    rmap.connect('rst_help',
+                 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
+                 _static=True)
     rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
 
     #ADMIN REPOSITORY REST ROUTES
@@ -101,8 +113,9 @@
                                             function=check_repo))
         #ajax delete repo perm user
         m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
-             action="delete_perm_user", conditions=dict(method=["DELETE"],
-                                                        function=check_repo))
+             action="delete_perm_user",
+             conditions=dict(method=["DELETE"], function=check_repo))
+
         #ajax delete repo perm users_group
         m.connect('delete_repo_users_group',
                   "/repos_delete_users_group/{repo_name:.*}",
@@ -111,18 +124,20 @@
 
         #settings actions
         m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
-             action="repo_stats", conditions=dict(method=["DELETE"],
-                                                        function=check_repo))
+                  action="repo_stats", conditions=dict(method=["DELETE"],
+                                                       function=check_repo))
         m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
-             action="repo_cache", conditions=dict(method=["DELETE"],
+                  action="repo_cache", conditions=dict(method=["DELETE"],
+                                                       function=check_repo))
+        m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*}",
+                  action="repo_public_journal", conditions=dict(method=["PUT"],
                                                         function=check_repo))
-        m.connect('repo_public_journal',
-                  "/repos_public_journal/{repo_name:.*}",
-                  action="repo_public_journal", conditions=dict(method=["PUT"],
-                  function=check_repo))
         m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
-             action="repo_pull", conditions=dict(method=["PUT"],
-                                                        function=check_repo))
+                  action="repo_pull", conditions=dict(method=["PUT"],
+                                                      function=check_repo))
+        m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*}",
+                  action="repo_as_fork", conditions=dict(method=["PUT"],
+                                                      function=check_repo))
 
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/repos_groups') as m:
@@ -155,6 +170,17 @@
         m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
                   action="show", conditions=dict(method=["GET"],
                                                  function=check_int))
+        # ajax delete repos group perm user
+        m.connect('delete_repos_group_user_perm',
+                  "/delete_repos_group_user_perm/{group_name:.*}",
+             action="delete_repos_group_user_perm",
+             conditions=dict(method=["DELETE"], function=check_group))
+
+        # ajax delete repos group perm users_group
+        m.connect('delete_repos_group_users_group_perm',
+                  "/delete_repos_group_users_group_perm/{group_name:.*}",
+                  action="delete_repos_group_users_group_perm",
+                  conditions=dict(method=["DELETE"], function=check_group))
 
     #ADMIN USER REST ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
@@ -267,6 +293,34 @@
         m.connect("admin_settings_create_repository", "/create_repository",
                   action="create_repository", conditions=dict(method=["GET"]))
 
+    #NOTIFICATION REST ROUTES
+    with rmap.submapper(path_prefix=ADMIN_PREFIX,
+                        controller='admin/notifications') as m:
+        m.connect("notifications", "/notifications",
+                  action="create", conditions=dict(method=["POST"]))
+        m.connect("notifications", "/notifications",
+                  action="index", conditions=dict(method=["GET"]))
+        m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
+                  action="mark_all_read", conditions=dict(method=["GET"]))
+        m.connect("formatted_notifications", "/notifications.{format}",
+                  action="index", conditions=dict(method=["GET"]))
+        m.connect("new_notification", "/notifications/new",
+                  action="new", conditions=dict(method=["GET"]))
+        m.connect("formatted_new_notification", "/notifications/new.{format}",
+                  action="new", conditions=dict(method=["GET"]))
+        m.connect("/notification/{notification_id}",
+                  action="update", conditions=dict(method=["PUT"]))
+        m.connect("/notification/{notification_id}",
+                  action="delete", conditions=dict(method=["DELETE"]))
+        m.connect("edit_notification", "/notification/{notification_id}/edit",
+                  action="edit", conditions=dict(method=["GET"]))
+        m.connect("formatted_edit_notification",
+                  "/notification/{notification_id}.{format}/edit",
+                  action="edit", conditions=dict(method=["GET"]))
+        m.connect("notification", "/notification/{notification_id}",
+                  action="show", conditions=dict(method=["GET"]))
+        m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
+                  action="show", conditions=dict(method=["GET"]))
 
     #ADMIN MAIN PAGES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
@@ -276,13 +330,12 @@
                   action='add_repo')
 
     #==========================================================================
-    # API V1
+    # API V2
     #==========================================================================
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='api/api') as m:
         m.connect('api', '/api')
 
-
     #USER JOURNAL
     rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
 
@@ -344,6 +397,16 @@
                 controller='changeset', revision='tip',
                 conditions=dict(function=check_repo))
 
+    rmap.connect('changeset_comment',
+                 '/{repo_name:.*}/changeset/{revision}/comment',
+                controller='changeset', revision='tip', action='comment',
+                conditions=dict(function=check_repo))
+
+    rmap.connect('changeset_comment_delete',
+                 '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
+                controller='changeset', action='delete_comment',
+                conditions=dict(function=check_repo, method=["DELETE"]))
+
     rmap.connect('raw_changeset_home',
                  '/{repo_name:.*}/raw-changeset/{revision}',
                  controller='changeset', action='raw_changeset',
@@ -361,6 +424,9 @@
     rmap.connect('tags_home', '/{repo_name:.*}/tags',
                 controller='tags', conditions=dict(function=check_repo))
 
+    rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
+                controller='bookmarks', conditions=dict(function=check_repo))
+
     rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
                 controller='changelog', conditions=dict(function=check_repo))
 
@@ -423,19 +489,19 @@
                 conditions=dict(function=check_repo))
 
     rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
-                controller='settings', action='fork_create',
+                controller='forks', action='fork_create',
                 conditions=dict(function=check_repo, method=["POST"]))
 
     rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
-                controller='settings', action='fork',
+                controller='forks', action='fork',
                 conditions=dict(function=check_repo))
 
+    rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
+                 controller='forks', action='forks',
+                 conditions=dict(function=check_repo))
+
     rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
                  controller='followers', action='followers',
                  conditions=dict(function=check_repo))
 
-    rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
-                 controller='forks', action='forks',
-                 conditions=dict(function=check_repo))
-
     return rmap
--- a/rhodecode/controllers/admin/admin.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/admin/admin.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 7, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
--- a/rhodecode/controllers/admin/ldap_settings.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/admin/ldap_settings.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Nov 26, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -39,7 +39,7 @@
 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
 from rhodecode.lib.exceptions import LdapImportError
 from rhodecode.model.forms import LdapSettingsForm
-from rhodecode.model.db import RhodeCodeSettings
+from rhodecode.model.db import RhodeCodeSetting
 
 log = logging.getLogger(__name__)
 
@@ -83,7 +83,7 @@
         super(LdapSettingsController, self).__before__()
 
     def index(self):
-        defaults = RhodeCodeSettings.get_ldap_settings()
+        defaults = RhodeCodeSetting.get_ldap_settings()
         c.search_scope_cur = defaults.get('ldap_search_scope')
         c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
         c.tls_kind_cur = defaults.get('ldap_tls_kind')
@@ -107,7 +107,7 @@
 
                 for k, v in form_result.items():
                     if k.startswith('ldap_'):
-                        setting = RhodeCodeSettings.get_by_name(k)
+                        setting = RhodeCodeSetting.get_by_name(k)
                         setting.app_settings_value = v
                         self.sa.add(setting)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/controllers/admin/notifications.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.controllers.admin.notifications
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    notifications controller for RhodeCode
+
+    :created_on: Nov 23, 2010
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import traceback
+
+from pylons import request
+from pylons import tmpl_context as c, url
+from pylons.controllers.util import redirect
+
+from rhodecode.lib.base import BaseController, render
+from rhodecode.model.db import Notification
+
+from rhodecode.model.notification import NotificationModel
+from rhodecode.lib.auth import LoginRequired, NotAnonymous
+from rhodecode.lib import helpers as h
+from rhodecode.model.meta import Session
+
+
+log = logging.getLogger(__name__)
+
+
+class NotificationsController(BaseController):
+    """REST Controller styled on the Atom Publishing Protocol"""
+    # To properly map this controller, ensure your config/routing.py
+    # file has a resource setup:
+    #     map.resource('notification', 'notifications', controller='_admin/notifications',
+    #         path_prefix='/_admin', name_prefix='_admin_')
+
+    @LoginRequired()
+    @NotAnonymous()
+    def __before__(self):
+        super(NotificationsController, self).__before__()
+
+    def index(self, format='html'):
+        """GET /_admin/notifications: All items in the collection"""
+        # url('notifications')
+        c.user = self.rhodecode_user
+        c.notifications = NotificationModel()\
+                            .get_for_user(self.rhodecode_user.user_id)
+        return render('admin/notifications/notifications.html')
+
+    def mark_all_read(self):
+        if request.environ.get('HTTP_X_PARTIAL_XHR'):
+            nm = NotificationModel()
+            # mark all read
+            nm.mark_all_read_for_user(self.rhodecode_user.user_id)
+            Session.commit()
+            c.user = self.rhodecode_user
+            c.notifications = nm.get_for_user(self.rhodecode_user.user_id)
+            return render('admin/notifications/notifications_data.html')
+
+    def create(self):
+        """POST /_admin/notifications: Create a new item"""
+        # url('notifications')
+
+    def new(self, format='html'):
+        """GET /_admin/notifications/new: Form to create a new item"""
+        # url('new_notification')
+
+    def update(self, notification_id):
+        """PUT /_admin/notifications/id: Update an existing item"""
+        # Forms posted to this method should contain a hidden field:
+        #    <input type="hidden" name="_method" value="PUT" />
+        # Or using helpers:
+        #    h.form(url('notification', notification_id=ID),
+        #           method='put')
+        # url('notification', notification_id=ID)
+
+    def delete(self, notification_id):
+        """DELETE /_admin/notifications/id: Delete an existing item"""
+        # Forms posted to this method should contain a hidden field:
+        #    <input type="hidden" name="_method" value="DELETE" />
+        # Or using helpers:
+        #    h.form(url('notification', notification_id=ID),
+        #           method='delete')
+        # url('notification', notification_id=ID)
+
+        try:
+            no = Notification.get(notification_id)
+            owner = lambda: (no.notifications_to_users.user.user_id
+                             == c.rhodecode_user.user_id)
+            if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
+                    NotificationModel().delete(c.rhodecode_user.user_id, no)
+                    Session.commit()
+                    return 'ok'
+        except Exception:
+            Session.rollback()
+            log.error(traceback.format_exc())
+        return 'fail'
+
+    def show(self, notification_id, format='html'):
+        """GET /_admin/notifications/id: Show a specific item"""
+        # url('notification', notification_id=ID)
+        c.user = self.rhodecode_user
+        no = Notification.get(notification_id)
+
+        owner = lambda: (no.notifications_to_users.user.user_id
+                         == c.user.user_id)
+        if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
+            unotification = NotificationModel()\
+                            .get_user_notification(c.user.user_id, no)
+
+            # if this association to user is not valid, we don't want to show
+            # this message
+            if unotification:
+                if unotification.read is False:
+                    unotification.mark_as_read()
+                    Session.commit()
+                c.notification = no
+
+                return render('admin/notifications/show_notification.html')
+
+        return redirect(url('notifications'))
+
+    def edit(self, notification_id, format='html'):
+        """GET /_admin/notifications/id/edit: Form to edit an existing item"""
+        # url('edit_notification', notification_id=ID)
--- a/rhodecode/controllers/admin/permissions.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/admin/permissions.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 27, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -38,6 +38,7 @@
 from rhodecode.model.forms import DefaultPermissionsForm
 from rhodecode.model.permission import PermissionModel
 from rhodecode.model.db import User
+from rhodecode.model.meta import Session
 
 log = logging.getLogger(__name__)
 
@@ -101,6 +102,7 @@
             form_result = _form.to_python(dict(request.POST))
             form_result.update({'perm_user_name': id})
             permission_model.update(form_result)
+            Session.commit()
             h.flash(_('Default permissions updated successfully'),
                     category='success')
 
--- a/rhodecode/controllers/admin/repos.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/admin/repos.py	Sun Feb 26 17:25:09 2012 +0200
@@ -3,11 +3,11 @@
     rhodecode.controllers.admin.repos
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    Admin controller for RhodeCode
+    Repositories controller for RhodeCode
 
     :created_on: Apr 7, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -29,9 +29,10 @@
 from formencode import htmlfill
 
 from paste.httpexceptions import HTTPInternalServerError
-from pylons import request, response, session, tmpl_context as c, url
-from pylons.controllers.util import abort, redirect
+from pylons import request, session, tmpl_context as c, url
+from pylons.controllers.util import redirect
 from pylons.i18n.translation import _
+from sqlalchemy.exc import IntegrityError
 
 from rhodecode.lib import helpers as h
 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
@@ -39,11 +40,11 @@
 from rhodecode.lib.base import BaseController, render
 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
 from rhodecode.lib.helpers import get_token
-from rhodecode.model.db import User, Repository, UserFollowing, Group
+from rhodecode.model.meta import Session
+from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
 from rhodecode.model.forms import RepoForm
 from rhodecode.model.scm import ScmModel
 from rhodecode.model.repo import RepoModel
-from sqlalchemy.exc import IntegrityError
 
 log = logging.getLogger(__name__)
 
@@ -63,9 +64,9 @@
         super(ReposController, self).__before__()
 
     def __load_defaults(self):
-        c.repo_groups = Group.groups_choices()
+        c.repo_groups = RepoGroup.groups_choices()
         c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
-        
+
         repo_model = RepoModel()
         c.users_array = repo_model.get_users_js()
         c.users_groups_array = repo_model.get_users_groups_js()
@@ -96,12 +97,13 @@
             .filter(UserFollowing.follows_repository == c.repo_info).scalar()
 
         if c.repo_info.stats:
-            last_rev = c.repo_info.stats.stat_on_revision
+            # this is on what revision we ended up so we add +1 for count
+            last_rev = c.repo_info.stats.stat_on_revision + 1
         else:
             last_rev = 0
         c.stats_revision = last_rev
 
-        c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
+        c.repo_last_rev = repo.count() if repo.revisions else 0
 
         if last_rev == 0 or c.repo_last_rev == 0:
             c.stats_percentage = 0
@@ -110,6 +112,10 @@
                                             c.repo_last_rev) * 100)
 
         defaults = RepoModel()._get_defaults(repo_name)
+
+        c.repos_list = [('', _('--REMOVE FORK--'))]
+        c.repos_list += [(x.repo_id, x.repo_name) for x in
+                   Repository.query().order_by(Repository.repo_name).all()]
         return defaults
 
     @HasPermissionAllDecorator('hg.admin')
@@ -127,13 +133,13 @@
         """
         POST /repos: Create a new item"""
         # url('repos')
-        repo_model = RepoModel()
+
         self.__load_defaults()
         form_result = {}
         try:
             form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
                             .to_python(dict(request.POST))
-            repo_model.create(form_result, self.rhodecode_user)
+            RepoModel().create(form_result, self.rhodecode_user)
             if form_result['clone_uri']:
                 h.flash(_('created repository %s from %s') \
                     % (form_result['repo_name'], form_result['clone_uri']),
@@ -143,13 +149,13 @@
                     category='success')
 
             if request.POST.get('user_created'):
-                #created by regular non admin user
+                # created by regular non admin user
                 action_logger(self.rhodecode_user, 'user_created_repo',
                               form_result['repo_name_full'], '', self.sa)
             else:
                 action_logger(self.rhodecode_user, 'admin_created_repo',
                               form_result['repo_name_full'], '', self.sa)
-
+            Session.commit()
         except formencode.Invalid, errors:
 
             c.new_repo = errors.value['repo_name']
@@ -207,7 +213,7 @@
             changed_name = repo.repo_name
             action_logger(self.rhodecode_user, 'admin_updated_repo',
                               changed_name, '', self.sa)
-
+            Session.commit()
         except formencode.Invalid, errors:
             defaults = self.__load_data(repo_name)
             defaults.update(errors.value)
@@ -251,9 +257,9 @@
             repo_model.delete(repo)
             invalidate_cache('get_repo_cached_%s' % repo_name)
             h.flash(_('deleted repository %s') % repo_name, category='success')
-
+            Session.commit()
         except IntegrityError, e:
-            if e.message.find('repositories_fork_id_fkey'):
+            if e.message.find('repositories_fork_id_fkey') != -1:
                 log.error(traceback.format_exc())
                 h.flash(_('Cannot delete %s it still contains attached '
                           'forks') % repo_name,
@@ -271,8 +277,7 @@
 
         return redirect(url('repos'))
 
-
-    @HasRepoPermissionAllDecorator('repository.admin')   
+    @HasRepoPermissionAllDecorator('repository.admin')
     def delete_perm_user(self, repo_name):
         """
         DELETE an existing repository permission user
@@ -281,9 +286,11 @@
         """
 
         try:
-            repo_model = RepoModel()
-            repo_model.delete_perm_user(request.POST, repo_name)
-        except Exception, e:
+            RepoModel().revoke_user_permission(repo=repo_name,
+                                               user=request.POST['user_id'])
+            Session.commit()
+        except Exception:
+            log.error(traceback.format_exc())
             h.flash(_('An error occurred during deletion of repository user'),
                     category='error')
             raise HTTPInternalServerError()
@@ -295,10 +302,14 @@
 
         :param repo_name:
         """
+
         try:
-            repo_model = RepoModel()
-            repo_model.delete_perm_users_group(request.POST, repo_name)
-        except Exception, e:
+            RepoModel().revoke_users_group_permission(
+                repo=repo_name, group_name=request.POST['users_group_id']
+            )
+            Session.commit()
+        except Exception:
+            log.error(traceback.format_exc())
             h.flash(_('An error occurred during deletion of repository'
                       ' users groups'),
                     category='error')
@@ -313,8 +324,8 @@
         """
 
         try:
-            repo_model = RepoModel()
-            repo_model.delete_stats(repo_name)
+            RepoModel().delete_stats(repo_name)
+            Session.commit()
         except Exception, e:
             h.flash(_('An error occurred during deletion of repository stats'),
                     category='error')
@@ -330,6 +341,7 @@
 
         try:
             ScmModel().mark_for_invalidation(repo_name)
+            Session.commit()
         except Exception, e:
             h.flash(_('An error occurred during cache invalidation'),
                     category='error')
@@ -353,6 +365,7 @@
                 self.scm_model.toggle_following_repo(repo_id, user_id)
                 h.flash(_('Updated repository visibility in public journal'),
                         category='success')
+                Session.commit()
             except:
                 h.flash(_('An error occurred during setting this'
                           ' repository in public journal'),
@@ -380,6 +393,28 @@
         return redirect(url('edit_repo', repo_name=repo_name))
 
     @HasPermissionAllDecorator('hg.admin')
+    def repo_as_fork(self, repo_name):
+        """
+        Mark given repository as a fork of another
+
+        :param repo_name:
+        """
+        try:
+            fork_id = request.POST.get('id_fork_of')
+            repo = ScmModel().mark_as_fork(repo_name, fork_id,
+                                    self.rhodecode_user.username)
+            fork = repo.fork.repo_name if repo.fork else _('Nothing')
+            Session.commit()
+            h.flash(_('Marked repo %s as fork of %s' % (repo_name,fork)),
+                    category='success')
+        except Exception, e:
+            raise
+            h.flash(_('An error occurred during this operation'),
+                    category='error')
+
+        return redirect(url('edit_repo', repo_name=repo_name))
+
+    @HasPermissionAllDecorator('hg.admin')
     def show(self, repo_name, format='html'):
         """GET /repos/repo_name: Show a specific item"""
         # url('repo', repo_name=ID)
--- a/rhodecode/controllers/admin/repos_groups.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/admin/repos_groups.py	Sun Feb 26 17:25:09 2012 +0200
@@ -1,22 +1,50 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.controllers.admin.repos_groups
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Repositories groups controller for RhodeCode
+
+    :created_on: Mar 23, 2010
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 import logging
 import traceback
 import formencode
 
 from formencode import htmlfill
-from operator import itemgetter
 
-from pylons import request, response, session, tmpl_context as c, url
-from pylons.controllers.util import abort, redirect
+from pylons import request, tmpl_context as c, url
+from pylons.controllers.util import redirect
 from pylons.i18n.translation import _
 
 from sqlalchemy.exc import IntegrityError
 
 from rhodecode.lib import helpers as h
-from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator
+from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
+    HasReposGroupPermissionAnyDecorator
 from rhodecode.lib.base import BaseController, render
-from rhodecode.model.db import Group
+from rhodecode.model.db import RepoGroup
 from rhodecode.model.repos_group import ReposGroupModel
 from rhodecode.model.forms import ReposGroupForm
+from rhodecode.model.meta import Session
+from rhodecode.model.repo import RepoModel
+from webob.exc import HTTPInternalServerError
 
 log = logging.getLogger(__name__)
 
@@ -32,9 +60,13 @@
         super(ReposGroupsController, self).__before__()
 
     def __load_defaults(self):
-        c.repo_groups = Group.groups_choices()
+        c.repo_groups = RepoGroup.groups_choices()
         c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
 
+        repo_model = RepoModel()
+        c.users_array = repo_model.get_users_js()
+        c.users_groups_array = repo_model.get_users_groups_js()
+
     def __load_data(self, group_id):
         """
         Load defaults settings for edit, and update
@@ -43,21 +75,30 @@
         """
         self.__load_defaults()
 
-        repo_group = Group.get(group_id)
+        repo_group = RepoGroup.get(group_id)
 
         data = repo_group.get_dict()
 
         data['group_name'] = repo_group.name
 
+        # fill repository users
+        for p in repo_group.repo_group_to_perm:
+            data.update({'u_perm_%s' % p.user.username:
+                             p.permission.permission_name})
+
+        # fill repository groups
+        for p in repo_group.users_group_to_perm:
+            data.update({'g_perm_%s' % p.users_group.users_group_name:
+                             p.permission.permission_name})
+
         return data
 
     @HasPermissionAnyDecorator('hg.admin')
     def index(self, format='html'):
         """GET /repos_groups: All items in the collection"""
         # url('repos_groups')
-
-        sk = lambda g:g.parents[0].group_name if g.parents else g.group_name
-        c.groups = sorted(Group.query().all(), key=sk)
+        sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
+        c.groups = sorted(RepoGroup.query().all(), key=sk)
         return render('admin/repos_groups/repos_groups_show.html')
 
     @HasPermissionAnyDecorator('hg.admin')
@@ -65,12 +106,16 @@
         """POST /repos_groups: Create a new item"""
         # url('repos_groups')
         self.__load_defaults()
-        repos_group_model = ReposGroupModel()
-        repos_group_form = ReposGroupForm(available_groups=
+        repos_group_form = ReposGroupForm(available_groups =
                                           c.repo_groups_choices)()
         try:
             form_result = repos_group_form.to_python(dict(request.POST))
-            repos_group_model.create(form_result)
+            ReposGroupModel().create(
+                    group_name=form_result['group_name'],
+                    group_description=form_result['group_description'],
+                    parent=form_result['group_parent_id']
+            )
+            Session.commit()
             h.flash(_('created repos group %s') \
                     % form_result['group_name'], category='success')
             #TODO: in futureaction_logger(, '', '', '', self.sa)
@@ -89,7 +134,6 @@
 
         return redirect(url('repos_groups'))
 
-
     @HasPermissionAnyDecorator('hg.admin')
     def new(self, format='html'):
         """GET /repos_groups/new: Form to create a new item"""
@@ -108,16 +152,17 @@
         # url('repos_group', id=ID)
 
         self.__load_defaults()
-        c.repos_group = Group.get(id)
+        c.repos_group = RepoGroup.get(id)
 
-        repos_group_model = ReposGroupModel()
-        repos_group_form = ReposGroupForm(edit=True,
-                                          old_data=c.repos_group.get_dict(),
-                                          available_groups=
-                                            c.repo_groups_choices)()
+        repos_group_form = ReposGroupForm(
+            edit=True,
+            old_data=c.repos_group.get_dict(),
+            available_groups=c.repo_groups_choices
+        )()
         try:
             form_result = repos_group_form.to_python(dict(request.POST))
-            repos_group_model.update(id, form_result)
+            ReposGroupModel().update(id, form_result)
+            Session.commit()
             h.flash(_('updated repos group %s') \
                     % form_result['group_name'], category='success')
             #TODO: in futureaction_logger(, '', '', '', self.sa)
@@ -136,7 +181,6 @@
 
         return redirect(url('repos_groups'))
 
-
     @HasPermissionAnyDecorator('hg.admin')
     def delete(self, id):
         """DELETE /repos_groups/id: Delete an existing item"""
@@ -147,8 +191,7 @@
         #           method='delete')
         # url('repos_group', id=ID)
 
-        repos_group_model = ReposGroupModel()
-        gr = Group.get(id)
+        gr = RepoGroup.get(id)
         repos = gr.repositories.all()
         if repos:
             h.flash(_('This group contains %s repositores and cannot be '
@@ -157,11 +200,12 @@
             return redirect(url('repos_groups'))
 
         try:
-            repos_group_model.delete(id)
+            ReposGroupModel().delete(id)
+            Session.commit()
             h.flash(_('removed repos group %s' % gr.group_name), category='success')
             #TODO: in future action_logger(, '', '', '', self.sa)
         except IntegrityError, e:
-            if e.message.find('groups_group_parent_id_fkey'):
+            if e.message.find('groups_group_parent_id_fkey') != -1:
                 log.error(traceback.format_exc())
                 h.flash(_('Cannot delete this group it still contains '
                           'subgroups'),
@@ -178,15 +222,57 @@
 
         return redirect(url('repos_groups'))
 
+    @HasReposGroupPermissionAnyDecorator('group.admin')
+    def delete_repos_group_user_perm(self, group_name):
+        """
+        DELETE an existing repositories group permission user
+
+        :param group_name:
+        """
+
+        try:
+            ReposGroupModel().revoke_user_permission(
+                repos_group=group_name, user=request.POST['user_id']
+            )
+            Session.commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('An error occurred during deletion of group user'),
+                    category='error')
+            raise HTTPInternalServerError()
+
+    @HasReposGroupPermissionAnyDecorator('group.admin')
+    def delete_repos_group_users_group_perm(self, group_name):
+        """
+        DELETE an existing repositories group permission users group
+
+        :param group_name:
+        """
+
+        try:
+            ReposGroupModel().revoke_users_group_permission(
+                repos_group=group_name,
+                group_name=request.POST['users_group_id']
+            )
+            Session.commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('An error occurred during deletion of group'
+                      ' users groups'),
+                    category='error')
+            raise HTTPInternalServerError()
+
     def show_by_name(self, group_name):
-        id_ = Group.get_by_group_name(group_name).group_id
+        id_ = RepoGroup.get_by_group_name(group_name).group_id
         return self.show(id_)
 
+    @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
+                                         'group.admin')
     def show(self, id, format='html'):
         """GET /repos_groups/id: Show a specific item"""
         # url('repos_group', id=ID)
 
-        c.group = Group.get(id)
+        c.group = RepoGroup.get(id)
 
         if c.group:
             c.group_repos = c.group.repositories.all()
@@ -201,8 +287,8 @@
 
         c.repo_cnt = 0
 
-        c.groups = self.sa.query(Group).order_by(Group.group_name)\
-            .filter(Group.group_parent_id == id).all()
+        c.groups = self.sa.query(RepoGroup).order_by(RepoGroup.group_name)\
+            .filter(RepoGroup.group_parent_id == id).all()
 
         return render('admin/repos_groups/repos_groups.html')
 
@@ -213,11 +299,11 @@
 
         id_ = int(id)
 
-        c.repos_group = Group.get(id_)
+        c.repos_group = RepoGroup.get(id_)
         defaults = self.__load_data(id_)
 
         # we need to exclude this group from the group list for editing
-        c.repo_groups = filter(lambda x:x[0] != id_, c.repo_groups)
+        c.repo_groups = filter(lambda x: x[0] != id_, c.repo_groups)
 
         return htmlfill.render(
             render('admin/repos_groups/repos_groups_edit.html'),
@@ -225,5 +311,3 @@
             encoding="UTF-8",
             force_defaults=False
         )
-
-
--- a/rhodecode/controllers/admin/settings.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/admin/settings.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Jul 14, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -40,13 +40,15 @@
 from rhodecode.lib.celerylib import tasks, run_task
 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
     set_rhodecode_config, repo_name_slug
-from rhodecode.model.db import RhodeCodeUi, Repository, Group, \
-    RhodeCodeSettings
+from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
+    RhodeCodeSetting
 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
     ApplicationUiSettingsForm
 from rhodecode.model.scm import ScmModel
 from rhodecode.model.user import UserModel
 from rhodecode.model.db import User
+from rhodecode.model.notification import EmailNotificationModel
+from rhodecode.model.meta import Session
 
 log = logging.getLogger(__name__)
 
@@ -69,7 +71,7 @@
         """GET /admin/settings: All items in the collection"""
         # url('admin_settings')
 
-        defaults = RhodeCodeSettings.get_app_settings()
+        defaults = RhodeCodeSetting.get_app_settings()
         defaults.update(self.get_hg_ui_settings())
         return htmlfill.render(
             render('admin/settings/settings.html'),
@@ -99,7 +101,7 @@
         # url('admin_setting', setting_id=ID)
         if setting_id == 'mapping':
             rm_obsolete = request.POST.get('destroy', False)
-            log.debug('Rescanning directories with destroy=%s', rm_obsolete)
+            log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
             initial = ScmModel().repo_scan()
             log.debug('invalidating all repositories')
             for repo_name in initial.keys():
@@ -124,15 +126,15 @@
                 form_result = application_form.to_python(dict(request.POST))
 
                 try:
-                    hgsettings1 = RhodeCodeSettings.get_by_name('title')
+                    hgsettings1 = RhodeCodeSetting.get_by_name('title')
                     hgsettings1.app_settings_value = \
                         form_result['rhodecode_title']
 
-                    hgsettings2 = RhodeCodeSettings.get_by_name('realm')
+                    hgsettings2 = RhodeCodeSetting.get_by_name('realm')
                     hgsettings2.app_settings_value = \
                         form_result['rhodecode_realm']
 
-                    hgsettings3 = RhodeCodeSettings.get_by_name('ga_code')
+                    hgsettings3 = RhodeCodeSetting.get_by_name('ga_code')
                     hgsettings3.app_settings_value = \
                         form_result['rhodecode_ga_code']
 
@@ -226,12 +228,11 @@
                      prefix_error=False,
                      encoding="UTF-8")
 
-
         if setting_id == 'hooks':
             ui_key = request.POST.get('new_hook_ui_key')
             ui_value = request.POST.get('new_hook_ui_value')
             try:
-                
+
                 if ui_value and ui_key:
                     RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
                     h.flash(_('Added new hook'),
@@ -240,13 +241,14 @@
                 # check for edits
                 update = False
                 _d = request.POST.dict_of_lists()
-                for k, v in zip(_d.get('hook_ui_key',[]), _d.get('hook_ui_value_new',[])):
+                for k, v in zip(_d.get('hook_ui_key', []),
+                                _d.get('hook_ui_value_new', [])):
                     RhodeCodeUi.create_or_update_hook(k, v)
                     update = True
 
                 if update:
                     h.flash(_('Updated hooks'), category='success')
-
+                Session.commit()
             except:
                 log.error(traceback.format_exc())
                 h.flash(_('error occurred during hook creation'),
@@ -254,6 +256,21 @@
 
             return redirect(url('admin_edit_setting', setting_id='hooks'))
 
+        if setting_id == 'email':
+            test_email = request.POST.get('test_email')
+            test_email_subj = 'RhodeCode TestEmail'
+            test_email_body = 'RhodeCode Email test'
+
+            test_email_html_body = EmailNotificationModel()\
+                .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
+                                body=test_email_body)
+
+            recipients = [test_email] if [test_email] else None
+
+            run_task(tasks.send_email, recipients, test_email_subj,
+                     test_email_body, test_email_html_body)
+
+            h.flash(_('Email task created'), category='success')
         return redirect(url('admin_settings'))
 
     @HasPermissionAllDecorator('hg.admin')
@@ -268,8 +285,8 @@
         if setting_id == 'hooks':
             hook_id = request.POST.get('hook_id')
             RhodeCodeUi.delete(hook_id)
-            
-            
+
+
     @HasPermissionAllDecorator('hg.admin')
     def show(self, setting_id, format='html'):
         """
@@ -339,7 +356,7 @@
             user_model.update_my_account(uid, form_result)
             h.flash(_('Your account was updated successfully'),
                     category='success')
-
+            Session.commit()
         except formencode.Invalid, errors:
             c.user = User.get(self.rhodecode_user.user_id)
             all_repos = self.sa.query(Repository)\
@@ -366,7 +383,7 @@
     def create_repository(self):
         """GET /_admin/create_repository: Form to create a new item"""
 
-        c.repo_groups = Group.groups_choices()
+        c.repo_groups = RepoGroup.groups_choices()
         c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
 
         new_repo = request.GET.get('repo', '')
--- a/rhodecode/controllers/admin/users.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/admin/users.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 4, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -29,7 +29,7 @@
 
 from formencode import htmlfill
 from pylons import request, session, tmpl_context as c, url, config
-from pylons.controllers.util import abort, redirect
+from pylons.controllers.util import redirect
 from pylons.i18n.translation import _
 
 from rhodecode.lib.exceptions import DefaultUserException, \
@@ -38,9 +38,10 @@
 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
 from rhodecode.lib.base import BaseController, render
 
-from rhodecode.model.db import User, RepoToPerm, UserToPerm, Permission
+from rhodecode.model.db import User, Permission
 from rhodecode.model.forms import UserForm
 from rhodecode.model.user import UserModel
+from rhodecode.model.meta import Session
 
 log = logging.getLogger(__name__)
 
@@ -71,12 +72,13 @@
         # url('users')
 
         user_model = UserModel()
-        login_form = UserForm()()
+        user_form = UserForm()()
         try:
-            form_result = login_form.to_python(dict(request.POST))
+            form_result = user_form.to_python(dict(request.POST))
             user_model.create(form_result)
             h.flash(_('created user %s') % form_result['username'],
                     category='success')
+            Session.commit()
             #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
         except formencode.Invalid, errors:
             return htmlfill.render(
@@ -114,11 +116,11 @@
             form_result = _form.to_python(dict(request.POST))
             user_model.update(id, form_result)
             h.flash(_('User updated successfully'), category='success')
-
+            Session.commit()
         except formencode.Invalid, errors:
             e = errors.error_dict or {}
             perm = Permission.get_by_key('hg.create.repository')
-            e.update({'create_repo_perm': UserToPerm.has_perm(id, perm)})
+            e.update({'create_repo_perm': user_model.has_perm(id, perm)})
             return htmlfill.render(
                 render('admin/users/user_edit.html'),
                 defaults=errors.value,
@@ -144,6 +146,7 @@
         try:
             user_model.delete(id)
             h.flash(_('successfully deleted user'), category='success')
+            Session.commit()
         except (UserOwnsReposException, DefaultUserException), e:
             h.flash(str(e), category='warning')
         except Exception:
@@ -158,20 +161,19 @@
     def edit(self, id, format='html'):
         """GET /users/id/edit: Form to edit an existing item"""
         # url('edit_user', id=ID)
-        user_model = UserModel()
-        c.user = user_model.get(id)
+        c.user = User.get(id)
         if not c.user:
             return redirect(url('users'))
         if c.user.username == 'default':
             h.flash(_("You can't edit this user"), category='warning')
             return redirect(url('users'))
         c.user.permissions = {}
-        c.granted_permissions = user_model.fill_perms(c.user)\
+        c.granted_permissions = UserModel().fill_perms(c.user)\
             .permissions['global']
 
         defaults = c.user.get_dict()
         perm = Permission.get_by_key('hg.create.repository')
-        defaults.update({'create_repo_perm': UserToPerm.has_perm(id, perm)})
+        defaults.update({'create_repo_perm': UserModel().has_perm(id, perm)})
 
         return htmlfill.render(
             render('admin/users/user_edit.html'),
@@ -185,23 +187,24 @@
         # url('user_perm', id=ID, method='put')
 
         grant_perm = request.POST.get('create_repo_perm', False)
+        user_model = UserModel()
 
         if grant_perm:
             perm = Permission.get_by_key('hg.create.none')
-            UserToPerm.revoke_perm(id, perm)
+            user_model.revoke_perm(id, perm)
 
             perm = Permission.get_by_key('hg.create.repository')
-            UserToPerm.grant_perm(id, perm)
+            user_model.grant_perm(id, perm)
             h.flash(_("Granted 'repository create' permission to user"),
                     category='success')
-
+            Session.commit()
         else:
             perm = Permission.get_by_key('hg.create.repository')
-            UserToPerm.revoke_perm(id, perm)
+            user_model.revoke_perm(id, perm)
 
             perm = Permission.get_by_key('hg.create.none')
-            UserToPerm.grant_perm(id, perm)
+            user_model.grant_perm(id, perm)
             h.flash(_("Revoked 'repository create' permission to user"),
                     category='success')
-
+            Session.commit()
         return redirect(url('edit_user', id=id))
--- a/rhodecode/controllers/admin/users_groups.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/admin/users_groups.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Jan 25, 2011
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -33,12 +33,15 @@
 from pylons.i18n.translation import _
 
 from rhodecode.lib.exceptions import UsersGroupsAssignedException
-from rhodecode.lib import helpers as h
+from rhodecode.lib import helpers as h, safe_unicode
 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
 from rhodecode.lib.base import BaseController, render
 
+from rhodecode.model.users_group import UsersGroupModel
+
 from rhodecode.model.db import User, UsersGroup, Permission, UsersGroupToPerm
-from rhodecode.model.forms import UserForm, UsersGroupForm
+from rhodecode.model.forms import UsersGroupForm
+from rhodecode.model.meta import Session
 
 log = logging.getLogger(__name__)
 
@@ -70,10 +73,12 @@
         users_group_form = UsersGroupForm()()
         try:
             form_result = users_group_form.to_python(dict(request.POST))
-            UsersGroup.create(form_result)
+            UsersGroupModel().create(name=form_result['users_group_name'],
+                                     active=form_result['users_group_active'])
             h.flash(_('created users group %s') \
                     % form_result['users_group_name'], category='success')
             #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
+            Session.commit()
         except formencode.Invalid, errors:
             return htmlfill.render(
                 render('admin/users_groups/users_group_add.html'),
@@ -103,29 +108,33 @@
         # url('users_group', id=ID)
 
         c.users_group = UsersGroup.get(id)
-        c.group_members = [(x.user_id, x.user.username) for x in
-                           c.users_group.members]
+        c.group_members_obj = [x.user for x in c.users_group.members]
+        c.group_members = [(x.user_id, x.username) for x in
+                           c.group_members_obj]
 
         c.available_members = [(x.user_id, x.username) for x in
                                self.sa.query(User).all()]
+
+        available_members = [safe_unicode(x[0]) for x in c.available_members]
+
         users_group_form = UsersGroupForm(edit=True,
                                           old_data=c.users_group.get_dict(),
-                                          available_members=[str(x[0]) for x
-                                                in c.available_members])()
+                                          available_members=available_members)()
 
         try:
             form_result = users_group_form.to_python(request.POST)
-            UsersGroup.update(id, form_result)
+            UsersGroupModel().update(c.users_group, form_result)
             h.flash(_('updated users group %s') \
                         % form_result['users_group_name'],
                     category='success')
             #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
+            Session.commit()
         except formencode.Invalid, errors:
             e = errors.error_dict or {}
 
             perm = Permission.get_by_key('hg.create.repository')
             e.update({'create_repo_perm':
-                         UsersGroupToPerm.has_perm(id, perm)})
+                         UsersGroupModel().has_perm(id, perm)})
 
             return htmlfill.render(
                 render('admin/users_groups/users_group_edit.html'),
@@ -150,8 +159,9 @@
         # url('users_group', id=ID)
 
         try:
-            UsersGroup.delete(id)
+            UsersGroupModel().delete(id)
             h.flash(_('successfully deleted users group'), category='success')
+            Session.commit()
         except UsersGroupsAssignedException, e:
             h.flash(e, category='error')
         except Exception:
@@ -172,14 +182,15 @@
             return redirect(url('users_groups'))
 
         c.users_group.permissions = {}
-        c.group_members = [(x.user_id, x.user.username) for x in
-                           c.users_group.members]
+        c.group_members_obj = [x.user for x in c.users_group.members]
+        c.group_members = [(x.user_id, x.username) for x in
+                           c.group_members_obj]
         c.available_members = [(x.user_id, x.username) for x in
                                self.sa.query(User).all()]
         defaults = c.users_group.get_dict()
         perm = Permission.get_by_key('hg.create.repository')
         defaults.update({'create_repo_perm':
-                         UsersGroupToPerm.has_perm(id, perm)})
+                         UsersGroupModel().has_perm(c.users_group, perm)})
         return htmlfill.render(
             render('admin/users_groups/users_group_edit.html'),
             defaults=defaults,
@@ -195,20 +206,21 @@
 
         if grant_perm:
             perm = Permission.get_by_key('hg.create.none')
-            UsersGroupToPerm.revoke_perm(id, perm)
+            UsersGroupModel().revoke_perm(id, perm)
 
             perm = Permission.get_by_key('hg.create.repository')
-            UsersGroupToPerm.grant_perm(id, perm)
+            UsersGroupModel().grant_perm(id, perm)
             h.flash(_("Granted 'repository create' permission to user"),
                     category='success')
 
+            Session.commit()
         else:
             perm = Permission.get_by_key('hg.create.repository')
-            UsersGroupToPerm.revoke_perm(id, perm)
+            UsersGroupModel().revoke_perm(id, perm)
 
             perm = Permission.get_by_key('hg.create.none')
-            UsersGroupToPerm.grant_perm(id, perm)
+            UsersGroupModel().grant_perm(id, perm)
             h.flash(_("Revoked 'repository create' permission to user"),
                     category='success')
-
+            Session.commit()
         return redirect(url('edit_users_group', id=id))
--- a/rhodecode/controllers/api/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/api/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,19 +7,19 @@
 
     :created_on: Aug 20, 2011
     :author: marcink
-    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>    
+    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
     :license: GPLv3, see COPYING for more details.
 """
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
 # as published by the Free Software Foundation; version 2
 # of the License or (at your opinion) any later version of the license.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
@@ -62,7 +62,7 @@
     Generate a Response object with a JSON-RPC error body
     """
     from pylons.controllers.util import Response
-    resp = Response(body=json.dumps(dict(result=None, error=message)),
+    resp = Response(body=json.dumps(dict(id=None, result=None, error=message)),
                     status=code,
                     content_type='application/json')
     return resp
@@ -100,7 +100,7 @@
         else:
             length = environ['CONTENT_LENGTH'] or 0
             length = int(environ['CONTENT_LENGTH'])
-            log.debug('Content-Length: %s', length)
+            log.debug('Content-Length: %s' % length)
 
         if length == 0:
             log.debug("Content-Length is 0")
@@ -118,11 +118,13 @@
         # check AUTH based on API KEY
         try:
             self._req_api_key = json_body['api_key']
+            self._req_id = json_body['id']
             self._req_method = json_body['method']
             self._request_params = json_body['args']
-            log.debug('method: %s, params: %s',
-                      self._req_method,
-                      self._request_params)
+            log.debug(
+                'method: %s, params: %s' % (self._req_method,
+                                            self._request_params)
+            )
         except KeyError, e:
             return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
 
@@ -225,21 +227,26 @@
         if self._error is not None:
             raw_response = None
 
-        response = dict(result=raw_response,
+        response = dict(id=self._req_id, result=raw_response,
                         error=self._error)
 
         try:
             return json.dumps(response)
         except TypeError, e:
-            log.debug('Error encoding response: %s', e)
-            return json.dumps(dict(result=None,
-                                   error="Error encoding response"))
+            log.debug('Error encoding response: %s' % e)
+            return json.dumps(
+                dict(
+                    self._req_id,
+                    result=None,
+                    error="Error encoding response"
+                )
+            )
 
     def _find_method(self):
         """
         Return method named by `self._req_method` in controller if able
         """
-        log.debug('Trying to find JSON-RPC method: %s', self._req_method)
+        log.debug('Trying to find JSON-RPC method: %s' % self._req_method)
         if self._req_method.startswith('_'):
             raise AttributeError("Method not allowed")
 
@@ -253,4 +260,3 @@
             return func
         else:
             raise AttributeError("No such method: %s" % self._req_method)
-
--- a/rhodecode/controllers/api/api.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/api/api.py	Sun Feb 26 17:25:09 2012 +0200
@@ -30,17 +30,15 @@
 
 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
 from rhodecode.lib.auth import HasPermissionAllDecorator, \
-    HasPermissionAnyDecorator
+    HasPermissionAnyDecorator, PasswordGenerator
+
+from rhodecode.model.meta import Session
 from rhodecode.model.scm import ScmModel
-
-from rhodecode.model.db import User, UsersGroup, Group, Repository
+from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.user import UserModel
-from rhodecode.model.repo_permission import RepositoryPermissionModel
 from rhodecode.model.users_group import UsersGroupModel
-from rhodecode.model import users_group
 from rhodecode.model.repos_group import ReposGroupModel
-from sqlalchemy.orm.exc import NoResultFound
 
 
 log = logging.getLogger(__name__)
@@ -63,26 +61,26 @@
     """
 
     @HasPermissionAllDecorator('hg.admin')
-    def pull(self, apiuser, repo):
+    def pull(self, apiuser, repo_name):
         """
         Dispatch pull action on given repo
 
 
         :param user:
-        :param repo:
+        :param repo_name:
         """
 
-        if Repository.is_valid(repo) is False:
-            raise JSONRPCError('Unknown repo "%s"' % repo)
+        if Repository.is_valid(repo_name) is False:
+            raise JSONRPCError('Unknown repo "%s"' % repo_name)
 
         try:
-            ScmModel().pull_changes(repo, self.rhodecode_user.username)
-            return 'Pulled from %s' % repo
+            ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
+            return 'Pulled from %s' % repo_name
         except Exception:
-            raise JSONRPCError('Unable to pull changes from "%s"' % repo)
+            raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
 
     @HasPermissionAllDecorator('hg.admin')
-    def get_user(self, apiuser, username):
+    def get_user(self, apiuser, userid):
         """"
         Get a user by username
 
@@ -90,9 +88,9 @@
         :param username:
         """
 
-        user = User.get_by_username(username)
-        if not user:
-            return None
+        user = UserModel().get_user(userid)
+        if user is None:
+            return user
 
         return dict(
             id=user.user_id,
@@ -102,7 +100,7 @@
             email=user.email,
             active=user.active,
             admin=user.admin,
-            ldap=user.ldap_dn
+            ldap_dn=user.ldap_dn
         )
 
     @HasPermissionAllDecorator('hg.admin')
@@ -124,47 +122,85 @@
                     email=user.email,
                     active=user.active,
                     admin=user.admin,
-                    ldap=user.ldap_dn
+                    ldap_dn=user.ldap_dn
                 )
             )
         return result
 
     @HasPermissionAllDecorator('hg.admin')
-    def create_user(self, apiuser, username, password, firstname,
-                    lastname, email, active=True, admin=False, ldap_dn=None):
+    def create_user(self, apiuser, username, email, password, firstname=None,
+                    lastname=None, active=True, admin=False, ldap_dn=None):
         """
         Create new user
 
         :param apiuser:
         :param username:
         :param password:
+        :param email:
         :param name:
         :param lastname:
-        :param email:
         :param active:
         :param admin:
         :param ldap_dn:
         """
-
         if User.get_by_username(username):
             raise JSONRPCError("user %s already exist" % username)
 
+        if User.get_by_email(email, case_insensitive=True):
+            raise JSONRPCError("email %s already exist" % email)
+
+        if ldap_dn:
+            # generate temporary password if ldap_dn
+            password = PasswordGenerator().gen_password(length=8)
+
         try:
-            form_data = dict(username=username,
-                             password=password,
-                             active=active,
-                             admin=admin,
-                             name=firstname,
-                             lastname=lastname,
-                             email=email,
-                             ldap_dn=ldap_dn)
-            UserModel().create_ldap(username, password, ldap_dn, form_data)
-            return dict(msg='created new user %s' % username)
+            usr = UserModel().create_or_update(
+                username, password, email, firstname,
+                lastname, active, admin, ldap_dn
+            )
+            Session.commit()
+            return dict(
+                id=usr.user_id,
+                msg='created new user %s' % username
+            )
         except Exception:
             log.error(traceback.format_exc())
             raise JSONRPCError('failed to create user %s' % username)
 
     @HasPermissionAllDecorator('hg.admin')
+    def update_user(self, apiuser, userid, username, password, email,
+                    firstname, lastname, active, admin, ldap_dn):
+        """
+        Updates given user
+
+        :param apiuser:
+        :param username:
+        :param password:
+        :param email:
+        :param name:
+        :param lastname:
+        :param active:
+        :param admin:
+        :param ldap_dn:
+        """
+        if not UserModel().get_user(userid):
+            raise JSONRPCError("user %s does not exist" % username)
+
+        try:
+            usr = UserModel().create_or_update(
+                username, password, email, firstname,
+                lastname, active, admin, ldap_dn
+            )
+            Session.commit()
+            return dict(
+                id=usr.user_id,
+                msg='updated user %s' % username
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError('failed to update user %s' % username)
+
+    @HasPermissionAllDecorator('hg.admin')
     def get_users_group(self, apiuser, group_name):
         """"
         Get users group by name
@@ -190,7 +226,7 @@
                             ldap=user.ldap_dn))
 
         return dict(id=users_group.users_group_id,
-                    name=users_group.users_group_name,
+                    group_name=users_group.users_group_name,
                     active=users_group.users_group_active,
                     members=members)
 
@@ -217,41 +253,40 @@
                                 ldap=user.ldap_dn))
 
             result.append(dict(id=users_group.users_group_id,
-                                name=users_group.users_group_name,
+                                group_name=users_group.users_group_name,
                                 active=users_group.users_group_active,
                                 members=members))
         return result
 
     @HasPermissionAllDecorator('hg.admin')
-    def create_users_group(self, apiuser, name, active=True):
+    def create_users_group(self, apiuser, group_name, active=True):
         """
         Creates an new usergroup
 
-        :param name:
+        :param group_name:
         :param active:
         """
 
-        if self.get_users_group(apiuser, name):
-            raise JSONRPCError("users group %s already exist" % name)
+        if self.get_users_group(apiuser, group_name):
+            raise JSONRPCError("users group %s already exist" % group_name)
 
         try:
-            form_data = dict(users_group_name=name,
-                             users_group_active=active)
-            ug = UsersGroup.create(form_data)
+            ug = UsersGroupModel().create(name=group_name, active=active)
+            Session.commit()
             return dict(id=ug.users_group_id,
-                        msg='created new users group %s' % name)
+                        msg='created new users group %s' % group_name)
         except Exception:
             log.error(traceback.format_exc())
-            raise JSONRPCError('failed to create group %s' % name)
+            raise JSONRPCError('failed to create group %s' % group_name)
 
     @HasPermissionAllDecorator('hg.admin')
-    def add_user_to_users_group(self, apiuser, group_name, user_name):
+    def add_user_to_users_group(self, apiuser, group_name, username):
         """"
         Add a user to a group
 
-        :param apiuser
-        :param group_name
-        :param user_name
+        :param apiuser:
+        :param group_name:
+        :param username:
         """
 
         try:
@@ -259,32 +294,65 @@
             if not users_group:
                 raise JSONRPCError('unknown users group %s' % group_name)
 
-            try:
-                user = User.get_by_username(user_name)
-            except NoResultFound:
-                raise JSONRPCError('unknown user %s' % user_name)
+            user = User.get_by_username(username)
+            if user is None:
+                raise JSONRPCError('unknown user %s' % username)
 
             ugm = UsersGroupModel().add_user_to_group(users_group, user)
+            success = True if ugm != True else False
+            msg = 'added member %s to users group %s' % (username, group_name)
+            msg = msg if success else 'User is already in that group'
+            Session.commit()
 
-            return dict(id=ugm.users_group_member_id,
-                        msg='created new users group member')
+            return dict(
+                id=ugm.users_group_member_id if ugm != True else None,
+                success=success,
+                msg=msg
+            )
         except Exception:
             log.error(traceback.format_exc())
-            raise JSONRPCError('failed to create users group member')
+            raise JSONRPCError('failed to add users group member')
+
+    @HasPermissionAllDecorator('hg.admin')
+    def remove_user_from_users_group(self, apiuser, group_name, username):
+        """
+        Remove user from a group
+
+        :param apiuser
+        :param group_name
+        :param username
+        """
+
+        try:
+            users_group = UsersGroup.get_by_group_name(group_name)
+            if not users_group:
+                raise JSONRPCError('unknown users group %s' % group_name)
+
+            user = User.get_by_username(username)
+            if user is None:
+                raise JSONRPCError('unknown user %s' % username)
+
+            success = UsersGroupModel().remove_user_from_group(users_group, user)
+            msg = 'removed member %s from users group %s' % (username, group_name)
+            msg = msg if success else "User wasn't in group"
+            Session.commit()
+            return dict(success=success, msg=msg)
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError('failed to remove user from group')
 
     @HasPermissionAnyDecorator('hg.admin')
-    def get_repo(self, apiuser, name):
+    def get_repo(self, apiuser, repoid):
         """"
         Get repository by name
 
-        :param apiuser
-        :param repo_name
+        :param apiuser:
+        :param repo_name:
         """
 
-        try:
-            repo = Repository.get_by_repo_name(name)
-        except NoResultFound:
-            return None
+        repo = RepoModel().get_repo(repoid)
+        if repo is None:
+            raise JSONRPCError('unknown repository %s' % repo)
 
         members = []
         for user in repo.repo_to_perm:
@@ -319,7 +387,7 @@
 
         return dict(
             id=repo.repo_id,
-            name=repo.repo_name,
+            repo_name=repo.repo_name,
             type=repo.repo_type,
             description=repo.description,
             members=members
@@ -330,7 +398,7 @@
         """"
         Get all repositories
 
-        :param apiuser
+        :param apiuser:
         """
 
         result = []
@@ -338,85 +406,255 @@
             result.append(
                 dict(
                     id=repository.repo_id,
-                    name=repository.repo_name,
+                    repo_name=repository.repo_name,
                     type=repository.repo_type,
                     description=repository.description
                 )
             )
         return result
 
-    @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
-    def create_repo(self, apiuser, name, owner_name, description='', 
-                    repo_type='hg', private=False):
+    @HasPermissionAnyDecorator('hg.admin')
+    def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
+                       ret_type='all'):
         """
-        Create a repository
+        returns a list of nodes and it's children
+        for a given path at given revision. It's possible to specify ret_type
+        to show only files or dirs
 
-        :param apiuser
-        :param name
-        :param description
-        :param type
-        :param private
-        :param owner_name
+        :param apiuser:
+        :param repo_name: name of repository
+        :param revision: revision for which listing should be done
+        :param root_path: path from which start displaying
+        :param ret_type: return type 'all|files|dirs' nodes
+        """
+        try:
+            _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
+                                          flat=False)
+            _map = {
+                'all': _d + _f,
+                'files': _f,
+                'dirs': _d,
+            }
+            return _map[ret_type]
+        except KeyError:
+            raise JSONRPCError('ret_type must be one of %s' % _map.keys())
+        except Exception, e:
+            raise JSONRPCError(e)
+
+    @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
+    def create_repo(self, apiuser, repo_name, owner_name, description='',
+                    repo_type='hg', private=False, clone_uri=None):
+        """
+        Create repository, if clone_url is given it makes a remote clone
+
+        :param apiuser:
+        :param repo_name:
+        :param owner_name:
+        :param description:
+        :param repo_type:
+        :param private:
+        :param clone_uri:
         """
 
         try:
-            try:
-                owner = User.get_by_username(owner_name)
-            except NoResultFound:
-                raise JSONRPCError('unknown user %s' % owner)
+            owner = User.get_by_username(owner_name)
+            if owner is None:
+                raise JSONRPCError('unknown user %s' % owner_name)
 
-            if self.get_repo(apiuser, name):
-                raise JSONRPCError("repo %s already exist" % name)
+            if Repository.get_by_repo_name(repo_name):
+                raise JSONRPCError("repo %s already exist" % repo_name)
 
-            groups = name.split('/')
+            groups = repo_name.split('/')
             real_name = groups[-1]
             groups = groups[:-1]
             parent_id = None
             for g in groups:
-                group = Group.get_by_group_name(g)
+                group = RepoGroup.get_by_group_name(g)
                 if not group:
-                    group = ReposGroupModel().create(dict(group_name=g,
-                                                  group_description='',
-                                                  group_parent_id=parent_id))
+                    group = ReposGroupModel().create(g, '', parent_id)
                 parent_id = group.group_id
 
-            RepoModel().create(dict(repo_name=real_name,
-                                     repo_name_full=name,
-                                     description=description,
-                                     private=private,
-                                     repo_type=repo_type,
-                                     repo_group=parent_id,
-                                     clone_uri=None), owner)
+            repo = RepoModel().create(
+                dict(
+                    repo_name=real_name,
+                    repo_name_full=repo_name,
+                    description=description,
+                    private=private,
+                    repo_type=repo_type,
+                    repo_group=parent_id,
+                    clone_uri=clone_uri
+                ),
+                owner
+            )
+            Session.commit()
+
+            return dict(
+                id=repo.repo_id,
+                msg="Created new repository %s" % repo.repo_name
+            )
+
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError('failed to create repository %s' % repo_name)
+
+    @HasPermissionAnyDecorator('hg.admin')
+    def delete_repo(self, apiuser, repo_name):
+        """
+        Deletes a given repository
+
+        :param repo_name:
+        """
+        if not Repository.get_by_repo_name(repo_name):
+            raise JSONRPCError("repo %s does not exist" % repo_name)
+        try:
+            RepoModel().delete(repo_name)
+            Session.commit()
+            return dict(
+                msg='Deleted repository %s' % repo_name
+            )
         except Exception:
             log.error(traceback.format_exc())
-            raise JSONRPCError('failed to create repository %s' % name)
+            raise JSONRPCError('failed to delete repository %s' % repo_name)
 
     @HasPermissionAnyDecorator('hg.admin')
-    def add_user_to_repo(self, apiuser, repo_name, user_name, perm):
+    def grant_user_permission(self, apiuser, repo_name, username, perm):
+        """
+        Grant permission for user on given repository, or update existing one
+        if found
+
+        :param repo_name:
+        :param username:
+        :param perm:
         """
-        Add permission for a user to a repository
+
+        try:
+            repo = Repository.get_by_repo_name(repo_name)
+            if repo is None:
+                raise JSONRPCError('unknown repository %s' % repo)
+
+            user = User.get_by_username(username)
+            if user is None:
+                raise JSONRPCError('unknown user %s' % username)
+
+            RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
 
-        :param apiuser
-        :param repo_name
-        :param user_name
-        :param perm
+            Session.commit()
+            return dict(
+                msg='Granted perm: %s for user: %s in repo: %s' % (
+                    perm, username, repo_name
+                )
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError(
+                'failed to edit permission %(repo)s for %(user)s' % dict(
+                    user=username, repo=repo_name
+                )
+            )
+
+    @HasPermissionAnyDecorator('hg.admin')
+    def revoke_user_permission(self, apiuser, repo_name, username):
+        """
+        Revoke permission for user on given repository
+
+        :param repo_name:
+        :param username:
         """
 
         try:
-            try:
-                repo = Repository.get_by_repo_name(repo_name)
-            except NoResultFound:
+            repo = Repository.get_by_repo_name(repo_name)
+            if repo is None:
+                raise JSONRPCError('unknown repository %s' % repo)
+
+            user = User.get_by_username(username)
+            if user is None:
+                raise JSONRPCError('unknown user %s' % username)
+
+            RepoModel().revoke_user_permission(repo=repo_name, user=username)
+
+            Session.commit()
+            return dict(
+                msg='Revoked perm for user: %s in repo: %s' % (
+                    username, repo_name
+                )
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError(
+                'failed to edit permission %(repo)s for %(user)s' % dict(
+                    user=username, repo=repo_name
+                )
+            )
+
+    @HasPermissionAnyDecorator('hg.admin')
+    def grant_users_group_permission(self, apiuser, repo_name, group_name, perm):
+        """
+        Grant permission for users group on given repository, or update
+        existing one if found
+
+        :param repo_name:
+        :param group_name:
+        :param perm:
+        """
+
+        try:
+            repo = Repository.get_by_repo_name(repo_name)
+            if repo is None:
                 raise JSONRPCError('unknown repository %s' % repo)
 
-            try:
-                user = User.get_by_username(user_name)
-            except NoResultFound:
-                raise JSONRPCError('unknown user %s' % user)
+            user_group = UsersGroup.get_by_group_name(group_name)
+            if user_group is None:
+                raise JSONRPCError('unknown users group %s' % user_group)
 
-            RepositoryPermissionModel()\
-                .update_or_delete_user_permission(repo, user, perm)
+            RepoModel().grant_users_group_permission(repo=repo_name,
+                                                     group_name=group_name,
+                                                     perm=perm)
+
+            Session.commit()
+            return dict(
+                msg='Granted perm: %s for group: %s in repo: %s' % (
+                    perm, group_name, repo_name
+                )
+            )
         except Exception:
             log.error(traceback.format_exc())
-            raise JSONRPCError('failed to edit permission %(repo)s for %(user)s'
-                            % dict(user=user_name, repo=repo_name))
+            raise JSONRPCError(
+                'failed to edit permission %(repo)s for %(usersgr)s' % dict(
+                    usersgr=group_name, repo=repo_name
+                )
+            )
+
+    @HasPermissionAnyDecorator('hg.admin')
+    def revoke_users_group_permission(self, apiuser, repo_name, group_name):
+        """
+        Revoke permission for users group on given repository
+
+        :param repo_name:
+        :param group_name:
+        """
+
+        try:
+            repo = Repository.get_by_repo_name(repo_name)
+            if repo is None:
+                raise JSONRPCError('unknown repository %s' % repo)
 
+            user_group = UsersGroup.get_by_group_name(group_name)
+            if user_group is None:
+                raise JSONRPCError('unknown users group %s' % user_group)
+
+            RepoModel().revoke_users_group_permission(repo=repo_name,
+                                                      group_name=group_name)
+
+            Session.commit()
+            return dict(
+                msg='Revoked perm for group: %s in repo: %s' % (
+                    group_name, repo_name
+                )
+            )
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError(
+                'failed to edit permission %(repo)s for %(usersgr)s' % dict(
+                    usersgr=group_name, repo=repo_name
+                )
+            )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/controllers/bookmarks.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.controllers.bookmarks
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Bookmarks controller for rhodecode
+
+    :created_on: Dec 1, 2011
+    :author: marcink
+    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+import logging
+
+from pylons import tmpl_context as c
+
+from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
+from rhodecode.lib.base import BaseRepoController, render
+from rhodecode.lib.compat import OrderedDict
+from webob.exc import HTTPNotFound
+
+log = logging.getLogger(__name__)
+
+
+class BookmarksController(BaseRepoController):
+
+    @LoginRequired()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    def __before__(self):
+        super(BookmarksController, self).__before__()
+
+    def index(self):
+        if c.rhodecode_repo.alias != 'hg':
+            raise HTTPNotFound()
+
+        c.repo_bookmarks = OrderedDict()
+
+        bookmarks = [(name, c.rhodecode_repo.get_changeset(hash_)) for \
+                 name, hash_ in c.rhodecode_repo._repo._bookmarks.items()]
+        ordered_tags = sorted(bookmarks, key=lambda x: x[1].date, reverse=True)
+        for name, cs_book in ordered_tags:
+            c.repo_bookmarks[name] = cs_book
+
+        return render('bookmarks/bookmarks.html')
--- a/rhodecode/controllers/branches.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/branches.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 21, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -46,33 +46,30 @@
     def index(self):
 
         def _branchtags(localrepo):
-
-            bt = {}
             bt_closed = {}
-
             for bn, heads in localrepo.branchmap().iteritems():
                 tip = heads[-1]
-                if 'close' not in localrepo.changelog.read(tip)[5]:
-                    bt[bn] = tip
-                else:
+                if 'close' in localrepo.changelog.read(tip)[5]:
                     bt_closed[bn] = tip
-            return bt, bt_closed
+            return bt_closed
 
+        cs_g = c.rhodecode_repo.get_changeset
 
-        bt, bt_closed = _branchtags(c.rhodecode_repo._repo)
-        cs_g = c.rhodecode_repo.get_changeset
-        _branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),) for n, h in
-                     bt.items()]
+        c.repo_closed_branches = {}
+        if c.rhodecode_db_repo.repo_type == 'hg':
+            bt_closed = _branchtags(c.rhodecode_repo._repo)
+            _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),)
+                                for n, h in bt_closed.items()]
 
-        _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),) for n, h in
-                     bt_closed.items()]
+            c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
+                                                    key=lambda ctx: ctx[0],
+                                                    reverse=False))
 
+        _branches = [(safe_unicode(n), cs_g(h))
+                     for n, h in c.rhodecode_repo.branches.items()]
         c.repo_branches = OrderedDict(sorted(_branches,
                                              key=lambda ctx: ctx[0],
                                              reverse=False))
-        c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
-                                                    key=lambda ctx: ctx[0],
-                                                    reverse=False))
 
 
         return render('branches/branches.html')
--- a/rhodecode/controllers/changelog.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/changelog.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 21, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -24,15 +24,22 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import logging
+import traceback
 
 from mercurial import graphmod
-from pylons import request, session, tmpl_context as c
+from pylons import request, url, session, tmpl_context as c
+from pylons.controllers.util import redirect
+from pylons.i18n.translation import _
 
+import rhodecode.lib.helpers as h
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.helpers import RepoPage
 from rhodecode.lib.compat import json
 
+from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
+from rhodecode.model.db import Repository
+
 log = logging.getLogger(__name__)
 
 
@@ -62,12 +69,32 @@
 
         p = int(request.params.get('page', 1))
         branch_name = request.params.get('branch', None)
-        c.total_cs = len(c.rhodecode_repo)
-        c.pagination = RepoPage(c.rhodecode_repo, page=p,
-                                item_count=c.total_cs, items_per_page=c.size,
-                                branch_name=branch_name)
+        try:
+            if branch_name:
+                collection = [z for z in
+                              c.rhodecode_repo.get_changesets(start=0,
+                                                    branch_name=branch_name)]
+                c.total_cs = len(collection)
+            else:
+                collection = c.rhodecode_repo
+                c.total_cs = len(c.rhodecode_repo)
 
-        self._graph(c.rhodecode_repo, c.total_cs, c.size, p)
+            c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
+                                    items_per_page=c.size, branch=branch_name)
+            collection = list(c.pagination)
+            page_revisions = [x.raw_id for x in collection]
+            c.comments = c.rhodecode_db_repo.comments(page_revisions)
+
+        except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
+            log.error(traceback.format_exc())
+            h.flash(str(e), category='warning')
+            return redirect(url('home'))
+
+        self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
+
+        c.branch_name = branch_name
+        c.branch_filters = [('', _('All Branches'))] + \
+            [(k, k) for k in c.rhodecode_repo.branches.keys()]
 
         return render('changelog/changelog.html')
 
@@ -76,7 +103,7 @@
             c.cs = c.rhodecode_repo.get_changeset(cs)
             return render('changelog/changelog_details.html')
 
-    def _graph(self, repo, repo_size, size, p):
+    def _graph(self, repo, collection, repo_size, size, p):
         """
         Generates a DAG graph for mercurial
 
@@ -84,29 +111,20 @@
         :param size: number of commits to show
         :param p: page number
         """
-        if not repo.revisions:
+        if not collection:
             c.jsdata = json.dumps([])
             return
 
-        revcount = min(repo_size, size)
-        offset = 1 if p == 1 else  ((p - 1) * revcount + 1)
-        try:
-            rev_end = repo.revisions.index(repo.revisions[(-1 * offset)])
-        except IndexError:
-            rev_end = repo.revisions.index(repo.revisions[-1])
-        rev_start = max(0, rev_end - revcount)
-
         data = []
-        rev_end += 1
+        revs = [x.revision for x in collection]
 
         if repo.alias == 'git':
-            for _ in xrange(rev_start, rev_end):
+            for _ in revs:
                 vtx = [0, 1]
                 edges = [[0, 0, 1]]
                 data.append(['', vtx, edges])
 
         elif repo.alias == 'hg':
-            revs = list(reversed(xrange(rev_start, rev_end)))
             c.dag = graphmod.colored(graphmod.dagwalker(repo._repo, revs))
             for (id, type, ctx, vtx, edges) in c.dag:
                 if type != graphmod.CHANGESET:
--- a/rhodecode/controllers/changeset.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/changeset.py	Sun Feb 26 17:25:09 2012 +0200
@@ -8,7 +8,7 @@
 
     :created_on: Apr 25, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -25,25 +25,129 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import logging
 import traceback
+from collections import defaultdict
+from webob.exc import HTTPForbidden
 
 from pylons import tmpl_context as c, url, request, response
 from pylons.i18n.translation import _
 from pylons.controllers.util import redirect
+from pylons.decorators import jsonify
+
+from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
+    ChangesetDoesNotExistError
+from rhodecode.lib.vcs.nodes import FileNode
 
 import rhodecode.lib.helpers as h
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.utils import EmptyChangeset
 from rhodecode.lib.compat import OrderedDict
-
-from vcs.exceptions import RepositoryError, ChangesetError, \
-ChangesetDoesNotExistError
-from vcs.nodes import FileNode
-from vcs.utils import diffs as differ
+from rhodecode.lib import diffs
+from rhodecode.model.db import ChangesetComment
+from rhodecode.model.comment import ChangesetCommentsModel
+from rhodecode.model.meta import Session
+from rhodecode.lib.diffs import wrapped_diff
 
 log = logging.getLogger(__name__)
 
 
+def anchor_url(revision, path):
+    fid = h.FID(revision, path)
+    return h.url.current(anchor=fid, **request.GET)
+
+
+def get_ignore_ws(fid, GET):
+    ig_ws_global = request.GET.get('ignorews')
+    ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
+    if ig_ws:
+        try:
+            return int(ig_ws[0].split(':')[-1])
+        except:
+            pass
+    return ig_ws_global
+
+
+def _ignorews_url(fileid=None):
+
+    params = defaultdict(list)
+    lbl = _('show white space')
+    ig_ws = get_ignore_ws(fileid, request.GET)
+    ln_ctx = get_line_ctx(fileid, request.GET)
+    # global option
+    if fileid is None:
+        if ig_ws is None:
+            params['ignorews'] += [1]
+            lbl = _('ignore white space')
+        ctx_key = 'context'
+        ctx_val = ln_ctx
+    # per file options
+    else:
+        if ig_ws is None:
+            params[fileid] += ['WS:1']
+            lbl = _('ignore white space')
+
+        ctx_key = fileid
+        ctx_val = 'C:%s' % ln_ctx
+    # if we have passed in ln_ctx pass it along to our params
+    if ln_ctx:
+        params[ctx_key] += [ctx_val]
+
+    params['anchor'] = fileid
+    img = h.image('/images/icons/text_strikethrough.png', lbl, class_='icon')
+    return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
+
+
+def get_line_ctx(fid, GET):
+    ln_ctx_global = request.GET.get('context')
+    ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
+
+    if ln_ctx:
+        retval = ln_ctx[0].split(':')[-1]
+    else:
+        retval = ln_ctx_global
+
+    try:
+        return int(retval)
+    except:
+        return
+
+
+def _context_url(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
+
+    params = defaultdict(list)
+
+    # global option
+    if fileid is None:
+        if ln_ctx > 0:
+            params['context'] += [ln_ctx]
+
+        if ig_ws:
+            ig_ws_key = 'ignorews'
+            ig_ws_val = 1
+
+    # per file option
+    else:
+        params[fileid] += ['C:%s' % ln_ctx]
+        ig_ws_key = fileid
+        ig_ws_val = 'WS:%s' % 1
+
+    if ig_ws:
+        params[ig_ws_key] += [ig_ws_val]
+
+    lbl = _('%s line context') % ln_ctx
+
+    params['anchor'] = fileid
+    img = h.image('/images/icons/table_add.png', lbl, class_='icon')
+    return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
+
+
 class ChangesetController(BaseRepoController):
 
     @LoginRequired()
@@ -55,20 +159,16 @@
 
     def index(self, revision):
 
-        def wrap_to_table(str):
-
-            return '''<table class="code-difftable">
-                        <tr class="line">
-                        <td class="lineno new"></td>
-                        <td class="code"><pre>%s</pre></td>
-                        </tr>
-                      </table>''' % str
+        c.anchor_url = anchor_url
+        c.ignorews_url = _ignorews_url
+        c.context_url = _context_url
 
         #get ranges of revisions if preset
         rev_range = revision.split('...')[:2]
-
+        enable_comments = True
         try:
             if len(rev_range) == 2:
+                enable_comments = False
                 rev_start = rev_range[0]
                 rev_end = rev_range[1]
                 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
@@ -77,6 +177,8 @@
                 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
 
             c.cs_ranges = list(rev_ranges)
+            if not c.cs_ranges:
+                raise RepositoryError('Changeset range returned empty result')
 
         except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
             log.error(traceback.format_exc())
@@ -84,14 +186,25 @@
             return redirect(url('home'))
 
         c.changes = OrderedDict()
-        c.sum_added = 0
-        c.sum_removed = 0
-        c.lines_added = 0
-        c.lines_deleted = 0
+
+        c.lines_added = 0  # count of lines added
+        c.lines_deleted = 0  # count of lines removes
+
+        cumulative_diff = 0
         c.cut_off = False  # defines if cut off limit is reached
 
+        c.comments = []
+        c.inline_comments = []
+        c.inline_cnt = 0
         # Iterate over ranges (default changeset view is always one changeset)
         for changeset in c.cs_ranges:
+            c.comments.extend(ChangesetCommentsModel()\
+                              .get_comments(c.rhodecode_db_repo.repo_id,
+                                            changeset.raw_id))
+            inlines = ChangesetCommentsModel()\
+                        .get_inline_comments(c.rhodecode_db_repo.repo_id,
+                                             changeset.raw_id)
+            c.inline_comments.extend(inlines)
             c.changes[changeset.raw_id] = []
             try:
                 changeset_parent = changeset.parents[0]
@@ -102,32 +215,19 @@
             # ADDED FILES
             #==================================================================
             for node in changeset.added:
-
-                filenode_old = FileNode(node.path, '', EmptyChangeset())
-                if filenode_old.is_binary or node.is_binary:
-                    diff = wrap_to_table(_('binary file'))
-                    st = (0, 0)
-                else:
-                    # in this case node.size is good parameter since those are
-                    # added nodes and their size defines how many changes were
-                    # made
-                    c.sum_added += node.size
-                    if c.sum_added < self.cut_off_limit:
-                        f_gitdiff = differ.get_gitdiff(filenode_old, node)
-                        d = differ.DiffProcessor(f_gitdiff, format='gitdiff')
-
-                        st = d.stat()
-                        diff = d.as_html()
-
-                    else:
-                        diff = wrap_to_table(_('Changeset is to big and '
-                                               'was cut off, see raw '
-                                               'changeset instead'))
-                        c.cut_off = True
-                        break
-
-                cs1 = None
-                cs2 = node.last_changeset.raw_id
+                fid = h.FID(revision, node.path)
+                line_context_lcl = get_line_ctx(fid, request.GET)
+                ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
+                lim = self.cut_off_limit
+                if cumulative_diff > self.cut_off_limit:
+                    lim = -1
+                size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
+                                         filenode_new=node,
+                                         cut_off_limit=lim,
+                                         ignore_whitespace=ign_whitespace_lcl,
+                                         line_context=line_context_lcl,
+                                         enable_comments=enable_comments)
+                cumulative_diff += size
                 c.lines_added += st[0]
                 c.lines_deleted += st[1]
                 c.changes[changeset.raw_id].append(('added', node, diff,
@@ -136,55 +236,42 @@
             #==================================================================
             # CHANGED FILES
             #==================================================================
-            if not c.cut_off:
-                for node in changeset.changed:
-                    try:
-                        filenode_old = changeset_parent.get_node(node.path)
-                    except ChangesetError:
-                        log.warning('Unable to fetch parent node for diff')
-                        filenode_old = FileNode(node.path, '',
-                                                EmptyChangeset())
-
-                    if filenode_old.is_binary or node.is_binary:
-                        diff = wrap_to_table(_('binary file'))
-                        st = (0, 0)
-                    else:
+            for node in changeset.changed:
+                try:
+                    filenode_old = changeset_parent.get_node(node.path)
+                except ChangesetError:
+                    log.warning('Unable to fetch parent node for diff')
+                    filenode_old = FileNode(node.path, '', EmptyChangeset())
 
-                        if c.sum_removed < self.cut_off_limit:
-                            f_gitdiff = differ.get_gitdiff(filenode_old, node)
-                            d = differ.DiffProcessor(f_gitdiff,
-                                                     format='gitdiff')
-                            st = d.stat()
-                            if (st[0] + st[1]) * 256 > self.cut_off_limit:
-                                diff = wrap_to_table(_('Diff is to big '
-                                                       'and was cut off, see '
-                                                       'raw diff instead'))
-                            else:
-                                diff = d.as_html()
-
-                            if diff:
-                                c.sum_removed += len(diff)
-                        else:
-                            diff = wrap_to_table(_('Changeset is to big and '
-                                                   'was cut off, see raw '
-                                                   'changeset instead'))
-                            c.cut_off = True
-                            break
-
-                    cs1 = filenode_old.last_changeset.raw_id
-                    cs2 = node.last_changeset.raw_id
-                    c.lines_added += st[0]
-                    c.lines_deleted += st[1]
-                    c.changes[changeset.raw_id].append(('changed', node, diff,
-                                                        cs1, cs2, st))
+                fid = h.FID(revision, node.path)
+                line_context_lcl = get_line_ctx(fid, request.GET)
+                ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
+                lim = self.cut_off_limit
+                if cumulative_diff > self.cut_off_limit:
+                    lim = -1
+                size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
+                                         filenode_new=node,
+                                         cut_off_limit=lim,
+                                         ignore_whitespace=ign_whitespace_lcl,
+                                         line_context=line_context_lcl,
+                                         enable_comments=enable_comments)
+                cumulative_diff += size
+                c.lines_added += st[0]
+                c.lines_deleted += st[1]
+                c.changes[changeset.raw_id].append(('changed', node, diff,
+                                                    cs1, cs2, st))
 
             #==================================================================
             # REMOVED FILES
             #==================================================================
-            if not c.cut_off:
-                for node in changeset.removed:
-                    c.changes[changeset.raw_id].append(('removed', node, None,
-                                                        None, None, (0, 0)))
+            for node in changeset.removed:
+                c.changes[changeset.raw_id].append(('removed', node, None,
+                                                    None, None, (0, 0)))
+
+        # count inline comments
+        for path, lines in c.inline_comments:
+            for comments in lines.values():
+                c.inline_cnt += len(comments)
 
         if len(c.cs_ranges) == 1:
             c.changeset = c.cs_ranges[0]
@@ -197,6 +284,8 @@
     def raw_changeset(self, revision):
 
         method = request.GET.get('diff', 'show')
+        ignore_whitespace = request.GET.get('ignorews') == '1'
+        line_context = request.GET.get('context', 3)
         try:
             c.scm_type = c.rhodecode_repo.alias
             c.changeset = c.rhodecode_repo.get_changeset(revision)
@@ -215,8 +304,10 @@
                 if filenode_old.is_binary or node.is_binary:
                     diff = _('binary file') + '\n'
                 else:
-                    f_gitdiff = differ.get_gitdiff(filenode_old, node)
-                    diff = differ.DiffProcessor(f_gitdiff,
+                    f_gitdiff = diffs.get_gitdiff(filenode_old, node,
+                                           ignore_whitespace=ignore_whitespace,
+                                           context=line_context)
+                    diff = diffs.DiffProcessor(f_gitdiff,
                                                 format='gitdiff').raw_diff()
 
                 cs1 = None
@@ -228,8 +319,10 @@
                 if filenode_old.is_binary or node.is_binary:
                     diff = _('binary file')
                 else:
-                    f_gitdiff = differ.get_gitdiff(filenode_old, node)
-                    diff = differ.DiffProcessor(f_gitdiff,
+                    f_gitdiff = diffs.get_gitdiff(filenode_old, node,
+                                           ignore_whitespace=ignore_whitespace,
+                                           context=line_context)
+                    diff = diffs.DiffProcessor(f_gitdiff,
                                                 format='gitdiff').raw_diff()
 
                 cs1 = filenode_old.last_changeset.raw_id
@@ -250,3 +343,25 @@
             c.diffs += x[2]
 
         return render('changeset/raw_changeset.html')
+
+    def comment(self, repo_name, revision):
+        ChangesetCommentsModel().create(text=request.POST.get('text'),
+                                        repo_id=c.rhodecode_db_repo.repo_id,
+                                        user_id=c.rhodecode_user.user_id,
+                                        revision=revision,
+                                        f_path=request.POST.get('f_path'),
+                                        line_no=request.POST.get('line'))
+        Session.commit()
+        return redirect(h.url('changeset_home', repo_name=repo_name,
+                              revision=revision))
+
+    @jsonify
+    def delete_comment(self, repo_name, comment_id):
+        co = ChangesetComment.get(comment_id)
+        owner = lambda: co.author.user_id == c.rhodecode_user.user_id
+        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
+            ChangesetCommentsModel().delete(comment=co)
+            Session.commit()
+            return True
+        else:
+            raise HTTPForbidden()
--- a/rhodecode/controllers/error.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/error.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Dec 8, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -54,7 +54,7 @@
         resp = request.environ.get('pylons.original_response')
         c.rhodecode_name = config.get('rhodecode_title')
 
-        log.debug('### %s ###', resp.status)
+        log.debug('### %s ###' % resp.status)
 
         e = request.environ
         c.serv_p = r'%(protocol)s://%(host)s/' \
--- a/rhodecode/controllers/feed.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/feed.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 23, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -51,6 +51,11 @@
         self.ttl = "5"
         self.feed_nr = 10
 
+    def _get_title(self, cs):
+        return "R%s:%s - %s" % (
+            cs.revision, cs.short_id, cs.message
+        )
+
     def __changes(self, cs):
         changes = []
 
@@ -72,18 +77,21 @@
 
     def atom(self, repo_name):
         """Produce an atom-1.0 feed via feedgenerator module"""
-        feed = Atom1Feed(title=self.title % repo_name,
-                         link=url('summary_home', repo_name=repo_name,
-                                  qualified=True),
-                         description=self.description % repo_name,
-                         language=self.language,
-                         ttl=self.ttl)
-        desc_msg = []
+        feed = Atom1Feed(
+             title=self.title % repo_name,
+             link=url('summary_home', repo_name=repo_name,
+                      qualified=True),
+             description=self.description % repo_name,
+             language=self.language,
+             ttl=self.ttl
+        )
+
         for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
+            desc_msg = []
             desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
             desc_msg.append(self.__changes(cs))
 
-            feed.add_item(title=cs.message,
+            feed.add_item(title=self._get_title(cs),
                           link=url('changeset_home', repo_name=repo_name,
                                    revision=cs.raw_id, qualified=True),
                           author_name=cs.author,
@@ -94,18 +102,21 @@
 
     def rss(self, repo_name):
         """Produce an rss2 feed via feedgenerator module"""
-        feed = Rss201rev2Feed(title=self.title % repo_name,
-                         link=url('summary_home', repo_name=repo_name,
-                                  qualified=True),
-                         description=self.description % repo_name,
-                         language=self.language,
-                         ttl=self.ttl)
-        desc_msg = []
+        feed = Rss201rev2Feed(
+            title=self.title % repo_name,
+            link=url('summary_home', repo_name=repo_name,
+                     qualified=True),
+            description=self.description % repo_name,
+            language=self.language,
+            ttl=self.ttl
+        )
+
         for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
+            desc_msg = []
             desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
             desc_msg.append(self.__changes(cs))
 
-            feed.add_item(title=cs.message,
+            feed.add_item(title=self._get_title(cs),
                           link=url('changeset_home', repo_name=repo_name,
                                    revision=cs.raw_id, qualified=True),
                           author_name=cs.author,
--- a/rhodecode/controllers/files.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/files.py	Sun Feb 26 17:25:09 2012 +0200
@@ -27,25 +27,29 @@
 import logging
 import traceback
 
-from os.path import join as jn
-
-from pylons import request, response, session, tmpl_context as c, url
+from pylons import request, response, tmpl_context as c, url
 from pylons.i18n.translation import _
 from pylons.controllers.util import redirect
 from pylons.decorators import jsonify
 
-from vcs.conf import settings
-from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
-    EmptyRepositoryError, ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
-from vcs.nodes import FileNode, NodeKind
-from vcs.utils import diffs as differ
+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.compat import OrderedDict
 from rhodecode.lib 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.model.repo import RepoModel
+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__)
 
@@ -104,26 +108,6 @@
 
         return file_node
 
-
-    def __get_paths(self, changeset, starting_path):
-        """recursive walk in root dir and return a set of all path in that dir
-        based on repository walk function
-        """
-        _files = list()
-        _dirs = list()
-
-        try:
-            tip = changeset
-            for topnode, dirs, files in tip.walk(starting_path):
-                for f in files:
-                    _files.append(f.path)
-                for d in dirs:
-                    _dirs.append(d.path)
-        except RepositoryError, e:
-            log.debug(traceback.format_exc())
-            pass
-        return _dirs, _files
-
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
     def index(self, repo_name, revision, f_path):
@@ -162,9 +146,9 @@
 
         # files or dirs
         try:
-            c.files_list = c.changeset.get_node(f_path)
+            c.file = c.changeset.get_node(f_path)
 
-            if c.files_list.is_file():
+            if c.file.is_file():
                 c.file_history = self._get_node_history(c.changeset, f_path)
             else:
                 c.file_history = []
@@ -405,13 +389,19 @@
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
     def diff(self, repo_name, f_path):
-        diff1 = request.GET.get('diff1')
-        diff2 = request.GET.get('diff2')
+        ignore_whitespace = request.GET.get('ignorews') == '1'
+        line_context = request.GET.get('context', 3)
+        diff1 = request.GET.get('diff1', '')
+        diff2 = request.GET.get('diff2', '')
         c.action = request.GET.get('diff')
         c.no_changes = diff1 == diff2
         c.f_path = f_path
         c.big_diff = False
-
+        c.anchor_url = anchor_url
+        c.ignorews_url = _ignorews_url
+        c.context_url = _context_url
+        c.changes = OrderedDict()
+        c.changes[diff2] = []
         try:
             if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
                 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
@@ -427,12 +417,14 @@
                 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
                 node2 = FileNode('.', '', changeset=c.changeset_2)
         except RepositoryError:
-            return redirect(url('files_home',
-                                repo_name=c.repo_name, f_path=f_path))
+            return redirect(url('files_home', repo_name=c.repo_name,
+                                f_path=f_path))
 
         if c.action == 'download':
-            diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
-                                        format='gitdiff')
+            _diff = diffs.get_gitdiff(node1, node2,
+                                      ignore_whitespace=ignore_whitespace,
+                                      context=line_context)
+            diff = diffs.DiffProcessor(_diff, format='gitdiff')
 
             diff_name = '%s_vs_%s.diff' % (diff1, diff2)
             response.content_type = 'text/plain'
@@ -441,39 +433,28 @@
             return diff.raw_diff()
 
         elif c.action == 'raw':
-            diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
-                                        format='gitdiff')
+            _diff = diffs.get_gitdiff(node1, node2,
+                                      ignore_whitespace=ignore_whitespace,
+                                      context=line_context)
+            diff = diffs.DiffProcessor(_diff, format='gitdiff')
             response.content_type = 'text/plain'
             return diff.raw_diff()
 
-        elif c.action == 'diff':
-            if node1.is_binary or node2.is_binary:
-                c.cur_diff = _('Binary file')
-            elif node1.size > self.cut_off_limit or \
-                    node2.size > self.cut_off_limit:
-                c.cur_diff = ''
-                c.big_diff = True
-            else:
-                diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
-                                        format='gitdiff')
-                c.cur_diff = diff.as_html()
         else:
+            fid = h.FID(diff2, node2.path)
+            line_context_lcl = get_line_ctx(fid, request.GET)
+            ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
 
-            #default option
-            if node1.is_binary or node2.is_binary:
-                c.cur_diff = _('Binary file')
-            elif node1.size > self.cut_off_limit or \
-                    node2.size > self.cut_off_limit:
-                c.cur_diff = ''
-                c.big_diff = True
+            lim = request.GET.get('fulldiff') or self.cut_off_limit
+            _, cs1, cs2, diff, st = wrapped_diff(filenode_old=node1,
+                                         filenode_new=node2,
+                                         cut_off_limit=lim,
+                                         ignore_whitespace=ign_whitespace_lcl,
+                                         line_context=line_context_lcl,
+                                         enable_comments=False)
 
-            else:
-                diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
-                                        format='gitdiff')
-                c.cur_diff = diff.as_html()
+            c.changes = [('', node2, diff, cs1, cs2, st,)]
 
-        if not c.cur_diff and not c.big_diff:
-            c.no_changes = True
         return render('files/file_diff.html')
 
     def _get_node_history(self, cs, f_path):
@@ -485,18 +466,16 @@
         tags_group = ([], _("Tags"))
 
         for chs in changesets:
-            n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
+            n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, chs.branch)
             changesets_group[0].append((chs.raw_id, n_desc,))
 
         hist_l.append(changesets_group)
 
         for name, chs in c.rhodecode_repo.branches.items():
-            #chs = chs.split(':')[-1]
             branches_group[0].append((chs, name),)
         hist_l.append(branches_group)
 
         for name, chs in c.rhodecode_repo.tags.items():
-            #chs = chs.split(':')[-1]
             tags_group[0].append((chs, name),)
         hist_l.append(tags_group)
 
@@ -508,6 +487,6 @@
     def nodelist(self, repo_name, revision, f_path):
         if request.environ.get('HTTP_X_PARTIAL_XHR'):
             cs = self.__get_cs_or_redirect(revision, repo_name)
-            _d, _f = self.__get_paths(cs, f_path)
+            _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
+                                          flat=False)
             return _d + _f
-
--- a/rhodecode/controllers/followers.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/followers.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 23, 2011
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
--- a/rhodecode/controllers/forks.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/forks.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 23, 2011
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -23,13 +23,23 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import logging
+import formencode
+import traceback
+from formencode import htmlfill
 
-from pylons import tmpl_context as c, request
+from pylons import tmpl_context as c, request, url
+from pylons.controllers.util import redirect
+from pylons.i18n.translation import _
+
+import rhodecode.lib.helpers as h
 
 from rhodecode.lib.helpers import Page
-from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
+from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
+    NotAnonymous
 from rhodecode.lib.base import BaseRepoController, render
-from rhodecode.model.db import Repository, User, UserFollowing
+from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
+from rhodecode.model.repo import RepoModel
+from rhodecode.model.forms import RepoForkForm
 
 log = logging.getLogger(__name__)
 
@@ -37,11 +47,59 @@
 class ForksController(BaseRepoController):
 
     @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
     def __before__(self):
         super(ForksController, self).__before__()
 
+    def __load_defaults(self):
+        c.repo_groups = RepoGroup.groups_choices()
+        c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
+
+    def __load_data(self, repo_name=None):
+        """
+        Load defaults settings for edit, and update
+
+        :param repo_name:
+        """
+        self.__load_defaults()
+
+        c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
+        repo = db_repo.scm_instance
+
+        if c.repo_info is None:
+            h.flash(_('%s repository is not mapped to db perhaps'
+                      ' it was created or renamed from the filesystem'
+                      ' please run the application again'
+                      ' in order to rescan repositories') % repo_name,
+                      category='error')
+
+            return redirect(url('repos'))
+
+        c.default_user_id = User.get_by_username('default').user_id
+        c.in_public_journal = UserFollowing.query()\
+            .filter(UserFollowing.user_id == c.default_user_id)\
+            .filter(UserFollowing.follows_repository == c.repo_info).scalar()
+
+        if c.repo_info.stats:
+            last_rev = c.repo_info.stats.stat_on_revision+1
+        else:
+            last_rev = 0
+        c.stats_revision = last_rev
+
+        c.repo_last_rev = repo.count() if repo.revisions else 0
+
+        if last_rev == 0 or c.repo_last_rev == 0:
+            c.stats_percentage = 0
+        else:
+            c.stats_percentage = '%.2f' % ((float((last_rev)) /
+                                            c.repo_last_rev) * 100)
+
+        defaults = RepoModel()._get_defaults(repo_name)
+        # add prefix to fork
+        defaults['repo_name'] = 'fork-' + defaults['repo_name']
+        return defaults
+
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
     def forks(self, repo_name):
         p = int(request.params.get('page', 1))
         repo_id = c.rhodecode_db_repo.repo_id
@@ -54,3 +112,63 @@
             return c.forks_data
 
         return render('/forks/forks.html')
+
+    @NotAnonymous()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    def fork(self, repo_name):
+        c.repo_info = Repository.get_by_repo_name(repo_name)
+        if not c.repo_info:
+            h.flash(_('%s repository is not mapped to db perhaps'
+                      ' it was created or renamed from the file system'
+                      ' please run the application again'
+                      ' in order to rescan repositories') % repo_name,
+                      category='error')
+
+            return redirect(url('home'))
+
+        defaults = self.__load_data(repo_name)
+
+        return htmlfill.render(
+            render('forks/fork.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False
+        )
+
+
+    @NotAnonymous()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    def fork_create(self, repo_name):
+        self.__load_defaults()
+        c.repo_info = Repository.get_by_repo_name(repo_name)
+        _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
+                             repo_groups=c.repo_groups_choices,)()
+        form_result = {}
+        try:
+            form_result = _form.to_python(dict(request.POST))
+            # add org_path of repo so we can do a clone from it later
+            form_result['org_path'] = c.repo_info.repo_name
+
+            # create fork is done sometimes async on celery, db transaction
+            # management is handled there.
+            RepoModel().create_fork(form_result, self.rhodecode_user)
+            h.flash(_('forked %s repository as %s') \
+                      % (repo_name, form_result['repo_name']),
+                    category='success')
+        except formencode.Invalid, errors:
+            c.new_repo = errors.value['repo_name']
+
+            return htmlfill.render(
+                render('forks/fork.html'),
+                defaults=errors.value,
+                errors=errors.error_dict or {},
+                prefix_error=False,
+                encoding="UTF-8")
+        except Exception:
+            log.error(traceback.format_exc())
+            h.flash(_('An error occurred during repository forking %s') %
+                    repo_name, category='error')
+
+        return redirect(url('home'))
--- a/rhodecode/controllers/home.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/home.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Feb 18, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -24,14 +24,13 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import logging
-from operator import itemgetter
 
 from pylons import tmpl_context as c, request
 from paste.httpexceptions import HTTPBadRequest
 
 from rhodecode.lib.auth import LoginRequired
 from rhodecode.lib.base import BaseController, render
-from rhodecode.model.db import Group, Repository
+from rhodecode.model.db import Repository
 
 log = logging.getLogger(__name__)
 
@@ -43,10 +42,8 @@
         super(HomeController, self).__before__()
 
     def index(self):
-
         c.repos_list = self.scm_model.get_repos()
-
-        c.groups = Group.query().filter(Group.group_parent_id == None).all()
+        c.groups = self.scm_model.get_repos_groups()
 
         return render('/index.html')
 
@@ -58,3 +55,11 @@
             return render('/repo_switcher_list.html')
         else:
             return HTTPBadRequest()
+
+    def branch_tag_switcher(self, repo_name):
+        if request.is_xhr:
+            c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
+            c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
+            return render('/switch_to_list.html')
+        else:
+            return HTTPBadRequest()
--- a/rhodecode/controllers/journal.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/journal.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Nov 21, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -23,21 +23,24 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import logging
+from itertools import groupby
 
 from sqlalchemy import or_
-from sqlalchemy.orm import joinedload, make_transient
+from sqlalchemy.orm import joinedload
 from webhelpers.paginate import Page
-from itertools import groupby
+from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 
 from paste.httpexceptions import HTTPBadRequest
 from pylons import request, tmpl_context as c, response, url
 from pylons.i18n.translation import _
-from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 
 import rhodecode.lib.helpers as h
 from rhodecode.lib.auth import LoginRequired, NotAnonymous
 from rhodecode.lib.base import BaseController, render
-from rhodecode.model.db import UserLog, UserFollowing
+from rhodecode.model.db import UserLog, UserFollowing, Repository, User
+from rhodecode.model.meta import Session
+from sqlalchemy.sql.expression import func
+from rhodecode.model.scm import ScmModel
 
 log = logging.getLogger(__name__)
 
@@ -58,6 +61,13 @@
         # Return a rendered template
         p = int(request.params.get('page', 1))
 
+        c.user = User.get(self.rhodecode_user.user_id)
+        all_repos = self.sa.query(Repository)\
+                     .filter(Repository.user_id == c.user.user_id)\
+                     .order_by(func.lower(Repository.repo_name)).all()
+
+        c.user_repos = ScmModel().get_repos(all_repos)
+
         c.following = self.sa.query(UserFollowing)\
             .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
             .options(joinedload(UserFollowing.follows_repository))\
@@ -124,6 +134,7 @@
                 try:
                     self.scm_model.toggle_following_user(user_id,
                                                 self.rhodecode_user.user_id)
+                    Session.commit()
                     return 'ok'
                 except:
                     raise HTTPBadRequest()
@@ -133,11 +144,12 @@
                 try:
                     self.scm_model.toggle_following_repo(repo_id,
                                                 self.rhodecode_user.user_id)
+                    Session.commit()
                     return 'ok'
                 except:
                     raise HTTPBadRequest()
 
-        log.debug('token mismatch %s vs %s', cur_token, token)
+        log.debug('token mismatch %s vs %s' % (cur_token, token))
         raise HTTPBadRequest()
 
     @LoginRequired()
--- a/rhodecode/controllers/login.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/login.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 22, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -38,6 +38,7 @@
 from rhodecode.model.db import User
 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
 from rhodecode.model.user import UserModel
+from rhodecode.model.meta import Session
 
 
 log = logging.getLogger(__name__)
@@ -49,7 +50,7 @@
         super(LoginController, self).__before__()
 
     def index(self):
-        #redirect if already logged in
+        # redirect if already logged in
         c.came_from = request.GET.get('came_from', None)
 
         if self.rhodecode_user.is_authenticated \
@@ -58,21 +59,28 @@
             return redirect(url('home'))
 
         if request.POST:
-            #import Login Form validator class
+            # import Login Form validator class
             login_form = LoginForm()
             try:
                 c.form_result = login_form.to_python(dict(request.POST))
-                #form checks for username/password, now we're authenticated
+                # form checks for username/password, now we're authenticated
                 username = c.form_result['username']
                 user = User.get_by_username(username, case_insensitive=True)
                 auth_user = AuthUser(user.user_id)
                 auth_user.set_authenticated()
-                session['rhodecode_user'] = auth_user
+                cs = auth_user.get_cookie_store()
+                session['rhodecode_user'] = cs
+                # If they want to be remembered, update the cookie
+                if c.form_result['remember'] is not False:
+                    session.cookie_expires = False
+                    session._set_cookie_values()
+                session._update_cookie_out()
                 session.save()
 
-                log.info('user %s is now authenticated and stored in session',
-                         username)
+                log.info('user %s is now authenticated and stored in '
+                         'session, session attrs %s' % (username, cs))
                 user.update_lastlogin()
+                Session.commit()
 
                 if c.came_from:
                     return redirect(c.came_from)
@@ -92,7 +100,6 @@
     @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
                                'hg.register.manual_activate')
     def register(self):
-        user_model = UserModel()
         c.auto_active = False
         for perm in User.get_by_username('default').user_perms:
             if perm.permission.permission_name == 'hg.register.auto_activate':
@@ -105,9 +112,10 @@
             try:
                 form_result = register_form.to_python(dict(request.POST))
                 form_result['active'] = c.auto_active
-                user_model.create_registration(form_result)
+                UserModel().create_registration(form_result)
                 h.flash(_('You have successfully registered into rhodecode'),
                             category='success')
+                Session.commit()
                 return redirect(url('login_home'))
 
             except formencode.Invalid, errors:
@@ -121,13 +129,11 @@
         return render('/register.html')
 
     def password_reset(self):
-        user_model = UserModel()
         if request.POST:
-
             password_reset_form = PasswordResetForm()()
             try:
                 form_result = password_reset_form.to_python(dict(request.POST))
-                user_model.reset_password_link(form_result)
+                UserModel().reset_password_link(form_result)
                 h.flash(_('Your password reset link was sent'),
                             category='success')
                 return redirect(url('login_home'))
@@ -143,13 +149,11 @@
         return render('/password_reset.html')
 
     def password_reset_confirmation(self):
-
         if request.GET and request.GET.get('key'):
             try:
-                user_model = UserModel()
                 user = User.get_by_api_key(request.GET.get('key'))
                 data = dict(email=user.email)
-                user_model.reset_password(data)
+                UserModel().reset_password(data)
                 h.flash(_('Your password reset was successful, '
                           'new password has been sent to your email'),
                             category='success')
@@ -160,7 +164,6 @@
         return redirect(url('login_home'))
 
     def logout(self):
-        del session['rhodecode_user']
-        session.save()
-        log.info('Logging out and setting user as Empty')
+        session.delete()
+        log.info('Logging out and deleting session for user')
         redirect(url('home'))
--- a/rhodecode/controllers/search.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/search.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Aug 7, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -26,7 +26,7 @@
 import traceback
 
 from pylons.i18n.translation import _
-from pylons import request, config, session, tmpl_context as c
+from pylons import request, config, tmpl_context as c
 
 from rhodecode.lib.auth import LoginRequired
 from rhodecode.lib.base import BaseController, render
@@ -76,7 +76,7 @@
                     cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
                 try:
                     query = qp.parse(unicode(cur_query))
-
+                    # extract words for highlight
                     if isinstance(query, Phrase):
                         highlight_items.update(query.words)
                     elif isinstance(query, Prefix):
@@ -92,18 +92,22 @@
                     log.debug(highlight_items)
                     results = searcher.search(query)
                     res_ln = len(results)
-                    c.runtime = '%s results (%.3f seconds)' \
-                        % (res_ln, results.runtime)
+                    c.runtime = '%s results (%.3f seconds)' % (
+                        res_ln, results.runtime
+                    )
 
                     def url_generator(**kw):
                         return update_params("?q=%s&type=%s" \
                                            % (c.cur_query, c.cur_search), **kw)
 
                     c.formated_results = Page(
-                                ResultWrapper(search_type, searcher, matcher,
-                                              highlight_items),
-                                page=p, item_count=res_ln,
-                                items_per_page=10, url=url_generator)
+                        ResultWrapper(search_type, searcher, matcher,
+                                      highlight_items),
+                        page=p,
+                        item_count=res_ln,
+                        items_per_page=10,
+                        url=url_generator
+                    )
 
                 except QueryParserError:
                     c.runtime = _('Invalid search query. Try quoting it.')
@@ -117,5 +121,6 @@
                 log.error(traceback.format_exc())
                 c.runtime = _('An error occurred during this search operation')
 
+
         # Return a rendered template
         return render('/search/search.html')
--- a/rhodecode/controllers/settings.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/settings.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Jun 30, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -35,14 +35,14 @@
 
 import rhodecode.lib.helpers as h
 
-from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
-    HasRepoPermissionAnyDecorator, NotAnonymous
+from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.utils import invalidate_cache, action_logger
 
-from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
+from rhodecode.model.forms import RepoSettingsForm
 from rhodecode.model.repo import RepoModel
-from rhodecode.model.db import Group
+from rhodecode.model.db import RepoGroup
+from rhodecode.model.meta import Session
 
 log = logging.getLogger(__name__)
 
@@ -52,15 +52,15 @@
     @LoginRequired()
     def __before__(self):
         super(SettingsController, self).__before__()
-    
+
     def __load_defaults(self):
-        c.repo_groups = Group.groups_choices()
+        c.repo_groups = RepoGroup.groups_choices()
         c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
-        
+
         repo_model = RepoModel()
         c.users_array = repo_model.get_users_js()
         c.users_groups_array = repo_model.get_users_groups_js()
-        
+
     @HasRepoPermissionAllDecorator('repository.admin')
     def index(self, repo_name):
         repo_model = RepoModel()
@@ -89,15 +89,15 @@
     def update(self, repo_name):
         repo_model = RepoModel()
         changed_name = repo_name
-        
+
         self.__load_defaults()
-        
+
         _form = RepoSettingsForm(edit=True,
                                  old_data={'repo_name': repo_name},
                                  repo_groups=c.repo_groups_choices)()
         try:
             form_result = _form.to_python(dict(request.POST))
-            
+
             repo_model.update(repo_name, form_result)
             invalidate_cache('get_repo_cached_%s' % repo_name)
             h.flash(_('Repository %s updated successfully' % repo_name),
@@ -105,6 +105,7 @@
             changed_name = form_result['repo_name_full']
             action_logger(self.rhodecode_user, 'user_updated_repo',
                           changed_name, '', self.sa)
+            Session.commit()
         except formencode.Invalid, errors:
             c.repo_info = repo_model.get_by_repo_name(repo_name)
             c.users_array = repo_model.get_users_js()
@@ -148,61 +149,10 @@
             repo_model.delete(repo)
             invalidate_cache('get_repo_cached_%s' % repo_name)
             h.flash(_('deleted repository %s') % repo_name, category='success')
+            Session.commit()
         except Exception:
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during deletion of %s') % repo_name,
                     category='error')
 
         return redirect(url('home'))
-
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def fork(self, repo_name):
-        repo_model = RepoModel()
-        c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
-        if not repo:
-            h.flash(_('%s repository is not mapped to db perhaps'
-                      ' it was created or renamed from the file system'
-                      ' please run the application again'
-                      ' in order to rescan repositories') % repo_name,
-                      category='error')
-
-            return redirect(url('home'))
-
-        return render('settings/repo_fork.html')
-
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def fork_create(self, repo_name):
-        repo_model = RepoModel()
-        c.repo_info = repo_model.get_by_repo_name(repo_name)
-        _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type})()
-        form_result = {}
-        try:
-            form_result = _form.to_python(dict(request.POST))
-            form_result.update({'repo_name': repo_name})
-            repo_model.create_fork(form_result, self.rhodecode_user)
-            h.flash(_('forked %s repository as %s') \
-                      % (repo_name, form_result['fork_name']),
-                    category='success')
-            action_logger(self.rhodecode_user,
-                          'user_forked_repo:%s' % form_result['fork_name'],
-                           repo_name, '', self.sa)
-        except formencode.Invalid, errors:
-            c.new_repo = errors.value['fork_name']
-            r = render('settings/repo_fork.html')
-
-            return htmlfill.render(
-                r,
-                defaults=errors.value,
-                errors=errors.error_dict or {},
-                prefix_error=False,
-                encoding="UTF-8")
-        except Exception:
-            log.error(traceback.format_exc())
-            h.flash(_('An error occurred during repository forking %s') %
-                    repo_name, category='error')
-
-        return redirect(url('home'))
--- a/rhodecode/controllers/shortlog.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/shortlog.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 18, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -30,6 +30,7 @@
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.helpers import RepoPage
+from pylons.controllers.util import redirect
 
 log = logging.getLogger(__name__)
 
@@ -50,8 +51,11 @@
             return url('shortlog_home', repo_name=repo_name, size=size, **kw)
 
         c.repo_changesets = RepoPage(c.rhodecode_repo, page=p,
-                                                       items_per_page=size,
-                                                       url=url_generator)
+                                    items_per_page=size, url=url_generator)
+
+        if not c.repo_changesets:
+            return redirect(url('summary_home', repo_name=repo_name))
+
         c.shortlog_data = render('shortlog/shortlog_data.html')
         if request.environ.get('HTTP_X_PARTIAL_XHR'):
             return c.shortlog_data
--- a/rhodecode/controllers/summary.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/summary.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 18, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -23,23 +23,28 @@
 # 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 traceback
 import calendar
 import logging
 from time import mktime
-from datetime import datetime, timedelta, date
+from datetime import timedelta, date
+from itertools import product
+from urlparse import urlparse
 
-from vcs.exceptions import ChangesetError
+from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
+    NodeDoesNotExistError
 
-from pylons import tmpl_context as c, request, url
+from pylons import tmpl_context as c, request, url, config
 from pylons.i18n.translation import _
 
-from rhodecode.model.db import Statistics, Repository
-from rhodecode.model.repo import RepoModel
+from beaker.cache import cache_region, region_invalidate
 
+from rhodecode.model.db import Statistics, CacheInvalidation
+from rhodecode.lib import ALL_READMES, ALL_EXTS
 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
@@ -48,6 +53,10 @@
 
 log = logging.getLogger(__name__)
 
+README_FILES = [''.join([x[0][0], x[1][0]]) for x in
+                    sorted(list(product(ALL_READMES, ALL_EXTS)),
+                           key=lambda y:y[0][1] + y[1][1])]
+
 
 class SummaryController(BaseRepoController):
 
@@ -58,10 +67,7 @@
         super(SummaryController, self).__before__()
 
     def index(self, repo_name):
-
-        e = request.environ
         c.dbrepo = dbrepo = c.rhodecode_db_repo
-
         c.following = self.scm_model.is_following_repo(repo_name,
                                                 self.rhodecode_user.user_id)
 
@@ -72,26 +78,34 @@
                                      items_per_page=10, url=url_generator)
 
         if self.rhodecode_user.username == 'default':
-            #for default(anonymous) user we don't need to pass credentials
+            # for default(anonymous) user we don't need to pass credentials
             username = ''
             password = ''
         else:
             username = str(self.rhodecode_user.username)
             password = '@'
 
-        if e.get('wsgi.url_scheme') == 'https':
-            split_s = 'https://'
-        else:
-            split_s = 'http://'
+        parsed_url = urlparse(url.current(qualified=True))
+
+        default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
+
+        uri_tmpl = config.get('clone_uri', default_clone_uri)
+        uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
 
-        qualified_uri = [split_s] + [url.current(qualified=True)\
-                                     .split(split_s)[-1]]
-        uri = u'%(proto)s%(user)s%(pass)s%(rest)s' \
-                % {'user': username,
-                     'pass': password,
-                     'proto': qualified_uri[0],
-                     'rest': qualified_uri[1]}
+        uri_dict = {
+           'user': username,
+           'pass': password,
+           'scheme': parsed_url.scheme,
+           'netloc': parsed_url.netloc,
+           'path': parsed_url.path
+        }
+        uri = uri_tmpl % uri_dict
+        # generate another clone url by id
+        uri_dict.update({'path': '/_%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]:
             try:
@@ -161,8 +175,44 @@
         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)
         return render('summary/summary.html')
 
+    def __get_readme_data(self, repo):
+
+        @cache_region('long_term')
+        def _get_readme_from_cache(key):
+            readme_data = None
+            readme_file = None
+            log.debug('Fetching readme file')
+            try:
+                cs = repo.get_changeset('tip')
+                renderer = MarkupRenderer()
+                for f in README_FILES:
+                    try:
+                        readme = cs.get_node(f)
+                        readme_file = f
+                        readme_data = renderer.render(readme.content, f)
+                        log.debug('Found readme %s' % readme_file)
+                        break
+                    except NodeDoesNotExistError:
+                        continue
+            except ChangesetError:
+                pass
+            except EmptyRepositoryError:
+                pass
+            except Exception:
+                log.error(traceback.format_exc())
+
+            return readme_data, readme_file
+
+        key = repo.name + '_README'
+        inv = CacheInvalidation.invalidate(key)
+        if inv is not None:
+            region_invalidate(_get_readme_from_cache, None, key)
+            CacheInvalidation.set_valid(inv.cache_key)
+        return _get_readme_from_cache(key)
+
     def _get_download_links(self, repo):
 
         download_l = []
--- a/rhodecode/controllers/tags.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/controllers/tags.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 21, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
--- a/rhodecode/lib/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -24,6 +24,9 @@
 # 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
@@ -66,6 +69,34 @@
 
 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):
     """
@@ -107,7 +138,6 @@
             line = replace(line, '\r\n', '\r')
             line = replace(line, '\n', '\r')
     elif mode == 2:
-            import re
             line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
     return line
 
@@ -151,7 +181,7 @@
     return hashlib.sha1(username + salt).hexdigest()
 
 
-def safe_unicode(str_, from_encoding='utf8'):
+def safe_unicode(str_, from_encoding=None):
     """
     safe unicode function. Does few trick to turn str_ into unicode
 
@@ -165,6 +195,11 @@
     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:
@@ -184,10 +219,11 @@
     except (ImportError, UnicodeDecodeError, Exception):
         return unicode(str_, from_encoding, 'replace')
 
-def safe_str(unicode_, to_encoding='utf8'):
+
+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
 
@@ -199,9 +235,17 @@
     if not isinstance(unicode_, basestring):
         return str(unicode_)
 
+    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:
@@ -221,11 +265,10 @@
     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 
+    file based sqlite databases. This prevents errors on sqlite. This only
     applies to sqlalchemy versions < 0.7.0
 
     """
@@ -284,7 +327,7 @@
 def age(curdate):
     """
     turns a datetime into an age string.
-    
+
     :param curdate: datetime object
     :rtype: unicode
     :returns: unicode words describing age
@@ -293,7 +336,7 @@
     from datetime import datetime
     from webhelpers.date import time_ago_in_words
 
-    _ = lambda s:s
+    _ = lambda s: s
 
     if not curdate:
         return ''
@@ -310,7 +353,8 @@
     pos = 1
     for scale in agescales:
         if scale[1] <= age_seconds:
-            if pos == 6:pos = 5
+            if pos == 6:
+                pos = 5
             return '%s %s' % (time_ago_in_words(curdate,
                                                 agescales[pos][0]), _('ago'))
         pos += 1
@@ -321,10 +365,10 @@
 def uri_filter(uri):
     """
     Removes user:password from given url string
-    
+
     :param uri:
     :rtype: unicode
-    :returns: filtered list of strings  
+    :returns: filtered list of strings
     """
     if not uri:
         return ''
@@ -353,7 +397,7 @@
 def credentials_filter(uri):
     """
     Returns a url with removed credentials
-    
+
     :param uri:
     """
 
@@ -364,16 +408,17 @@
 
     return ''.join(uri)
 
+
 def get_changeset_safe(repo, rev):
     """
-    Safe version of get_changeset if this changeset doesn't exists for a 
+    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 vcs.backends.base import BaseRepository
-    from vcs.exceptions import RepositoryError
+    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))
@@ -390,13 +435,13 @@
     """
     Returns tuple of (number, id) from repository containing this package
     or None if repository could not be found.
-    
+
     :param quiet: prints error for fetching revision if True
     """
 
     try:
-        from vcs import get_repo
-        from vcs.utils.helpers import get_scm
+        from rhodecode.lib.vcs import get_repo
+        from rhodecode.lib.vcs.utils.helpers import get_scm
         repopath = os.path.join(os.path.dirname(__file__), '..', '..')
         scm = get_scm(repopath)[0]
         repo = get_repo(path=repopath, alias=scm)
@@ -408,3 +453,15 @@
                    "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())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/annotate.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.lib.annotate
+    ~~~~~~~~~~~~~~~~~~~~~~
+
+    Anontation library for usage in rhodecode, previously part of vcs
+
+    :created_on: Dec 4, 2011
+    :author: marcink
+    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.nodes import FileNode
+from pygments.formatters import HtmlFormatter
+from pygments import highlight
+
+import StringIO
+
+
+def annotate_highlight(filenode, annotate_from_changeset_func=None,
+        order=None, headers=None, **options):
+    """
+    Returns html portion containing annotated table with 3 columns: line
+    numbers, changeset information and pygmentized line of code.
+
+    :param filenode: FileNode object
+    :param annotate_from_changeset_func: function taking changeset and
+      returning single annotate cell; needs break line at the end
+    :param order: ordered sequence of ``ls`` (line numbers column),
+      ``annotate`` (annotate column), ``code`` (code column); Default is
+      ``['ls', 'annotate', 'code']``
+    :param headers: dictionary with headers (keys are whats in ``order``
+      parameter)
+    """
+    options['linenos'] = True
+    formatter = AnnotateHtmlFormatter(filenode=filenode, order=order,
+        headers=headers,
+        annotate_from_changeset_func=annotate_from_changeset_func, **options)
+    lexer = filenode.lexer
+    highlighted = highlight(filenode.content, lexer, formatter)
+    return highlighted
+
+
+class AnnotateHtmlFormatter(HtmlFormatter):
+
+    def __init__(self, filenode, annotate_from_changeset_func=None,
+            order=None, **options):
+        """
+        If ``annotate_from_changeset_func`` is passed it should be a function
+        which returns string from the given changeset. For example, we may pass
+        following function as ``annotate_from_changeset_func``::
+
+            def changeset_to_anchor(changeset):
+                return '<a href="/changesets/%s/">%s</a>\n' %\
+                       (changeset.id, changeset.id)
+
+        :param annotate_from_changeset_func: see above
+        :param order: (default: ``['ls', 'annotate', 'code']``); order of
+          columns;
+        :param options: standard pygment's HtmlFormatter options, there is
+          extra option tough, ``headers``. For instance we can pass::
+
+             formatter = AnnotateHtmlFormatter(filenode, headers={
+                'ls': '#',
+                'annotate': 'Annotate',
+                'code': 'Code',
+             })
+
+        """
+        super(AnnotateHtmlFormatter, self).__init__(**options)
+        self.annotate_from_changeset_func = annotate_from_changeset_func
+        self.order = order or ('ls', 'annotate', 'code')
+        headers = options.pop('headers', None)
+        if headers and not ('ls' in headers and 'annotate' in headers and
+            'code' in headers):
+            raise ValueError("If headers option dict is specified it must "
+                "all 'ls', 'annotate' and 'code' keys")
+        self.headers = headers
+        if isinstance(filenode, FileNode):
+            self.filenode = filenode
+        else:
+            raise VCSError("This formatter expect FileNode parameter, not %r"
+                % type(filenode))
+
+    def annotate_from_changeset(self, changeset):
+        """
+        Returns full html line for single changeset per annotated line.
+        """
+        if self.annotate_from_changeset_func:
+            return self.annotate_from_changeset_func(changeset)
+        else:
+            return ''.join((changeset.id, '\n'))
+
+    def _wrap_tablelinenos(self, inner):
+        dummyoutfile = StringIO.StringIO()
+        lncount = 0
+        for t, line in inner:
+            if t:
+                lncount += 1
+            dummyoutfile.write(line)
+
+        fl = self.linenostart
+        mw = len(str(lncount + fl - 1))
+        sp = self.linenospecial
+        st = self.linenostep
+        la = self.lineanchors
+        aln = self.anchorlinenos
+        if sp:
+            lines = []
+
+            for i in range(fl, fl + lncount):
+                if i % st == 0:
+                    if i % sp == 0:
+                        if aln:
+                            lines.append('<a href="#%s-%d" class="special">'
+                                         '%*d</a>' %
+                                         (la, i, mw, i))
+                        else:
+                            lines.append('<span class="special">'
+                                         '%*d</span>' % (mw, i))
+                    else:
+                        if aln:
+                            lines.append('<a href="#%s-%d">'
+                                         '%*d</a>' % (la, i, mw, i))
+                        else:
+                            lines.append('%*d' % (mw, i))
+                else:
+                    lines.append('')
+            ls = '\n'.join(lines)
+        else:
+            lines = []
+            for i in range(fl, fl + lncount):
+                if i % st == 0:
+                    if aln:
+                        lines.append('<a href="#%s-%d">%*d</a>' \
+                                     % (la, i, mw, i))
+                    else:
+                        lines.append('%*d' % (mw, i))
+                else:
+                    lines.append('')
+            ls = '\n'.join(lines)
+
+        annotate_changesets = [tup[1] for tup in self.filenode.annotate]
+        # If pygments cropped last lines break we need do that too
+        ln_cs = len(annotate_changesets)
+        ln_ = len(ls.splitlines())
+        if  ln_cs > ln_:
+            annotate_changesets = annotate_changesets[:ln_ - ln_cs]
+        annotate = ''.join((self.annotate_from_changeset(changeset)
+            for changeset in annotate_changesets))
+        # in case you wonder about the seemingly redundant <div> here:
+        # since the content in the other cell also is wrapped in a div,
+        # some browsers in some configurations seem to mess up the formatting.
+        '''
+        yield 0, ('<table class="%stable">' % self.cssclass +
+                  '<tr><td class="linenos"><div class="linenodiv"><pre>' +
+                  ls + '</pre></div></td>' +
+                  '<td class="code">')
+        yield 0, dummyoutfile.getvalue()
+        yield 0, '</td></tr></table>'
+
+        '''
+        headers_row = []
+        if self.headers:
+            headers_row = ['<tr class="annotate-header">']
+            for key in self.order:
+                td = ''.join(('<td>', self.headers[key], '</td>'))
+                headers_row.append(td)
+            headers_row.append('</tr>')
+
+        body_row_start = ['<tr>']
+        for key in self.order:
+            if key == 'ls':
+                body_row_start.append(
+                    '<td class="linenos"><div class="linenodiv"><pre>' +
+                    ls + '</pre></div></td>')
+            elif key == 'annotate':
+                body_row_start.append(
+                    '<td class="annotate"><div class="annotatediv"><pre>' +
+                    annotate + '</pre></div></td>')
+            elif key == 'code':
+                body_row_start.append('<td class="code">')
+        yield 0, ('<table class="%stable">' % self.cssclass +
+                  ''.join(headers_row) +
+                  ''.join(body_row_start)
+                  )
+        yield 0, dummyoutfile.getvalue()
+        yield 0, '</td></tr></table>'
--- a/rhodecode/lib/auth.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/auth.py	Sun Feb 26 17:25:09 2012 +0200
@@ -6,7 +6,8 @@
     authentication and permission libraries
 
     :created_on: Apr 4, 2010
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -30,11 +31,12 @@
 from tempfile import _RandomNameSequence
 from decorator import decorator
 
-from pylons import config, session, url, request
+from pylons import config, url, request
 from pylons.controllers.util import abort, redirect
 from pylons.i18n.translation import _
 
 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
+from rhodecode.model.meta import Session
 
 if __platform__ in PLATFORM_WIN:
     from hashlib import sha256
@@ -43,20 +45,22 @@
 
 from rhodecode.lib import str2bool, safe_unicode
 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
-from rhodecode.lib.utils import get_repo_slug
+from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
 from rhodecode.lib.auth_ldap import AuthLdap
 
 from rhodecode.model import meta
 from rhodecode.model.user import UserModel
-from rhodecode.model.db import Permission, RhodeCodeSettings, User
+from rhodecode.model.db import Permission, RhodeCodeSetting, User
 
 log = logging.getLogger(__name__)
 
 
 class PasswordGenerator(object):
-    """This is a simple class for generating password from
-        different sets of characters
-        usage:
+    """
+    This is a simple class for generating password from different sets of
+    characters
+    usage::
+
         passwd_gen = PasswordGenerator()
         #print 8-letter password containing only big and small letters
             of alphabet
@@ -128,15 +132,24 @@
     return RhodeCodeCrypto.hash_check(password, hashed)
 
 
-def generate_api_key(username, salt=None):
+def generate_api_key(str_, salt=None):
+    """
+    Generates API KEY from given string
+
+    :param str_:
+    :param salt:
+    """
+
     if salt is None:
         salt = _RandomNameSequence().next()
 
-    return hashlib.sha1(username + salt).hexdigest()
+    return hashlib.sha1(str_ + salt).hexdigest()
 
 
 def authfunc(environ, username, password):
-    """Dummy authentication function used in Mercurial/Git/ and access control,
+    """
+    Dummy authentication wrapper function used in Mercurial and Git for
+    access control.
 
     :param environ: needed only for using in Basic auth
     """
@@ -144,7 +157,8 @@
 
 
 def authenticate(username, password):
-    """Authentication function used for access control,
+    """
+    Authentication function used for access control,
     firstly checks for db authentication then if ldap is enabled for ldap
     authentication, also creates ldap user if not in database
 
@@ -159,16 +173,16 @@
     if user is not None and not user.ldap_dn:
         if user.active:
             if user.username == 'default' and user.active:
-                log.info('user %s authenticated correctly as anonymous user',
+                log.info('user %s authenticated correctly as anonymous user' %
                          username)
                 return True
 
             elif user.username == username and check_password(password,
                                                               user.password):
-                log.info('user %s authenticated correctly', username)
+                log.info('user %s authenticated correctly' % username)
                 return True
         else:
-            log.warning('user %s is disabled', username)
+            log.warning('user %s tried auth but is disabled' % username)
 
     else:
         log.debug('Regular authentication failed')
@@ -178,7 +192,7 @@
             log.debug('this user already exists as non ldap')
             return False
 
-        ldap_settings = RhodeCodeSettings.get_ldap_settings()
+        ldap_settings = RhodeCodeSetting.get_ldap_settings()
         #======================================================================
         # FALLBACK TO LDAP AUTH IF ENABLE
         #======================================================================
@@ -202,7 +216,7 @@
                 aldap = AuthLdap(**kwargs)
                 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
                                                                 password)
-                log.debug('Got ldap DN response %s', user_dn)
+                log.debug('Got ldap DN response %s' % user_dn)
 
                 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
                                                            .get(k), [''])[0]
@@ -222,6 +236,7 @@
                                           user_attrs):
                     log.info('created new ldap user %s' % username)
 
+                Session.commit()
                 return True
             except (LdapUsernameError, LdapPasswordError,):
                 pass
@@ -231,6 +246,64 @@
     return False
 
 
+def login_container_auth(username):
+    user = User.get_by_username(username)
+    if user is None:
+        user_attrs = {
+            'name': username,
+            'lastname': None,
+            'email': None,
+        }
+        user = UserModel().create_for_container_auth(username, user_attrs)
+        if not user:
+            return None
+        log.info('User %s was created by container authentication' % username)
+
+    if not user.active:
+        return None
+
+    user.update_lastlogin()
+    Session.commit()
+
+    log.debug('User %s is now logged in by container authentication',
+              user.username)
+    return user
+
+
+def get_container_username(environ, config):
+    username = None
+
+    if str2bool(config.get('container_auth_enabled', False)):
+        from paste.httpheaders import REMOTE_USER
+        username = REMOTE_USER(environ)
+
+    if not username and str2bool(config.get('proxypass_auth_enabled', False)):
+        username = environ.get('HTTP_X_FORWARDED_USER')
+
+    if username:
+        # Removing realm and domain from username
+        username = username.partition('@')[0]
+        username = username.rpartition('\\')[2]
+        log.debug('Received username %s from container' % username)
+
+    return username
+
+
+class CookieStoreWrapper(object):
+
+    def __init__(self, cookie_store):
+        self.cookie_store = cookie_store
+
+    def __repr__(self):
+        return 'CookieStore<%s>' % (self.cookie_store)
+
+    def get(self, key, other=None):
+        if isinstance(self.cookie_store, dict):
+            return self.cookie_store.get(key, other)
+        elif isinstance(self.cookie_store, AuthUser):
+            return self.cookie_store.__dict__.get(key, other)
+
+
 class  AuthUser(object):
     """
     A simple object that handles all attributes of user in RhodeCode
@@ -241,12 +314,12 @@
     in
     """
 
-    def __init__(self, user_id=None, api_key=None):
+    def __init__(self, user_id=None, api_key=None, username=None):
 
         self.user_id = user_id
         self.api_key = None
+        self.username = username
 
-        self.username = 'None'
         self.name = ''
         self.lastname = ''
         self.email = ''
@@ -255,51 +328,85 @@
         self.permissions = {}
         self._api_key = api_key
         self.propagate_data()
+        self._instance = None
 
     def propagate_data(self):
         user_model = UserModel()
-        self.anonymous_user = User.get_by_username('default')
+        self.anonymous_user = User.get_by_username('default', cache=True)
+        is_user_loaded = False
+
+        # try go get user by api key
         if self._api_key and self._api_key != self.anonymous_user.api_key:
-            #try go get user by api key
-            log.debug('Auth User lookup by API KEY %s', self._api_key)
-            user_model.fill_data(self, api_key=self._api_key)
-        else:
-            log.debug('Auth User lookup by USER ID %s', self.user_id)
-            if self.user_id is not None \
-                and self.user_id != self.anonymous_user.user_id:
-                user_model.fill_data(self, user_id=self.user_id)
+            log.debug('Auth User lookup by API KEY %s' % self._api_key)
+            is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
+        # lookup by userid
+        elif (self.user_id is not None and
+              self.user_id != self.anonymous_user.user_id):
+            log.debug('Auth User lookup by USER ID %s' % self.user_id)
+            is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
+        # lookup by username
+        elif self.username and \
+            str2bool(config.get('container_auth_enabled', False)):
+
+            log.debug('Auth User lookup by USER NAME %s' % self.username)
+            dbuser = login_container_auth(self.username)
+            if dbuser is not None:
+                for k, v in dbuser.get_dict().items():
+                    setattr(self, k, v)
+                self.set_authenticated()
+                is_user_loaded = True
+
+        if not is_user_loaded:
+            # if we cannot authenticate user try anonymous
+            if self.anonymous_user.active is True:
+                user_model.fill_data(self, user_id=self.anonymous_user.user_id)
+                # then we set this user is logged in
+                self.is_authenticated = True
             else:
-                if self.anonymous_user.active is True:
-                    user_model.fill_data(self,
-                                         user_id=self.anonymous_user.user_id)
-                    #then we set this user is logged in
-                    self.is_authenticated = True
-                else:
-                    self.is_authenticated = False
+                self.user_id = None
+                self.username = None
+                self.is_authenticated = False
 
-        log.debug('Auth User is now %s', self)
+        if not self.username:
+            self.username = 'None'
+
+        log.debug('Auth User is now %s' % self)
         user_model.fill_perms(self)
 
     @property
     def is_admin(self):
         return self.admin
 
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.name, self.lastname, self.email)
-
     def __repr__(self):
         return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
                                               self.is_authenticated)
 
     def set_authenticated(self, authenticated=True):
-
         if self.user_id != self.anonymous_user.user_id:
             self.is_authenticated = authenticated
 
+    def get_cookie_store(self):
+        return {'username': self.username,
+                'user_id': self.user_id,
+                'is_authenticated': self.is_authenticated}
+
+    @classmethod
+    def from_cookie_store(cls, cookie_store):
+        """
+        Creates AuthUser from a cookie store
+
+        :param cls:
+        :param cookie_store:
+        """
+        user_id = cookie_store.get('user_id')
+        username = cookie_store.get('username')
+        api_key = cookie_store.get('api_key')
+        return AuthUser(user_id, api_key, username)
+
 
 def set_available_permissions(config):
-    """This function will propagate pylons globals with all available defined
+    """
+    This function will propagate pylons globals with all available defined
     permission given in db. We don't want to check each time from db for new
     permissions since adding a new permission also requires application restart
     ie. to decorate new views with the newly created permission
@@ -309,9 +416,9 @@
     """
     log.info('getting information about all available permissions')
     try:
-        sa = meta.Session()
+        sa = meta.Session
         all_perms = sa.query(Permission).all()
-    except:
+    except Exception:
         pass
     finally:
         meta.Session.remove()
@@ -343,26 +450,31 @@
 
         api_access_ok = False
         if self.api_access:
-            log.debug('Checking API KEY access for %s', cls)
+            log.debug('Checking API KEY access for %s' % cls)
             if user.api_key == request.GET.get('api_key'):
                 api_access_ok = True
             else:
                 log.debug("API KEY token not valid")
-
-        log.debug('Checking if %s is authenticated @ %s', user.username, cls)
+        loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
+        log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
         if user.is_authenticated or api_access_ok:
-            log.debug('user %s is authenticated', user.username)
+            log.info('user %s is authenticated and granted access to %s' % (
+                       user.username, loc)
+            )
             return func(*fargs, **fkwargs)
         else:
-            log.warn('user %s NOT authenticated', user.username)
+            log.warn('user %s NOT authenticated on func: %s' % (
+                user, loc)
+            )
             p = url.current()
 
-            log.debug('redirecting to login page with %s', p)
+            log.debug('redirecting to login page with %s' % p)
             return redirect(url('login_home', came_from=p))
 
 
 class NotAnonymous(object):
-    """Must be logged in to execute this function else
+    """
+    Must be logged in to execute this function else
     redirect to login page"""
 
     def __call__(self, func):
@@ -372,7 +484,7 @@
         cls = fargs[0]
         self.user = cls.rhodecode_user
 
-        log.debug('Checking if user is not anonymous @%s', cls)
+        log.debug('Checking if user is not anonymous @%s' % cls)
 
         anonymous = self.user.username == 'default'
 
@@ -411,13 +523,11 @@
                self.user)
 
         if self.check_permissions():
-            log.debug('Permission granted for %s %s', cls, self.user)
+            log.debug('Permission granted for %s %s' % (cls, self.user))
             return func(*fargs, **fkwargs)
 
         else:
-            log.warning('Permission denied for %s %s', cls, self.user)
-
-
+            log.debug('Permission denied for %s %s' % (cls, self.user))
             anonymous = self.user.username == 'default'
 
             if anonymous:
@@ -430,7 +540,7 @@
                 return redirect(url('login_home', came_from=p))
 
             else:
-                #redirect with forbidden ret code
+                # redirect with forbidden ret code
                 return abort(403)
 
     def check_permissions(self):
@@ -439,7 +549,8 @@
 
 
 class HasPermissionAllDecorator(PermsDecorator):
-    """Checks for access permission for all given predicates. All of them
+    """
+    Checks for access permission for all given predicates. All of them
     have to be meet in order to fulfill the request
     """
 
@@ -450,7 +561,8 @@
 
 
 class HasPermissionAnyDecorator(PermsDecorator):
-    """Checks for access permission for any of given predicates. In order to
+    """
+    Checks for access permission for any of given predicates. In order to
     fulfill the request any of predicates must be meet
     """
 
@@ -461,7 +573,8 @@
 
 
 class HasRepoPermissionAllDecorator(PermsDecorator):
-    """Checks for access permission for all given predicates for specific
+    """
+    Checks for access permission for all given predicates for specific
     repository. All of them have to be meet in order to fulfill the request
     """
 
@@ -477,7 +590,8 @@
 
 
 class HasRepoPermissionAnyDecorator(PermsDecorator):
-    """Checks for access permission for any of given predicates for specific
+    """
+    Checks for access permission for any of given predicates for specific
     repository. In order to fulfill the request any of predicates must be meet
     """
 
@@ -493,6 +607,41 @@
         return False
 
 
+class HasReposGroupPermissionAllDecorator(PermsDecorator):
+    """
+    Checks for access permission for all given predicates for specific
+    repository. All of them have to be meet in order to fulfill the request
+    """
+
+    def check_permissions(self):
+        group_name = get_repos_group_slug(request)
+        try:
+            user_perms = set([self.user_perms['repositories_groups'][group_name]])
+        except KeyError:
+            return False
+        if self.required_perms.issubset(user_perms):
+            return True
+        return False
+
+
+class HasReposGroupPermissionAnyDecorator(PermsDecorator):
+    """
+    Checks for access permission for any of given predicates for specific
+    repository. In order to fulfill the request any of predicates must be meet
+    """
+
+    def check_permissions(self):
+        group_name = get_repos_group_slug(request)
+
+        try:
+            user_perms = set([self.user_perms['repositories_groups'][group_name]])
+        except KeyError:
+            return False
+        if self.required_perms.intersection(user_perms):
+            return True
+        return False
+
+
 #==============================================================================
 # CHECK FUNCTIONS
 #==============================================================================
@@ -511,7 +660,7 @@
         self.repo_name = None
 
     def __call__(self, check_Location=''):
-        user = session.get('rhodecode_user', False)
+        user = request.user
         if not user:
             return False
         self.user_perms = user.permissions
@@ -525,7 +674,7 @@
             return True
 
         else:
-            log.warning('Permission denied for %s @ %s', self.granted_for,
+            log.debug('Permission denied for %s @ %s', self.granted_for,
                         check_Location or 'unspecified location')
             return False
 
@@ -559,8 +708,9 @@
             self.repo_name = get_repo_slug(request)
 
         try:
-            self.user_perms = set([self.user_perms['reposit'
-                                                   'ories'][self.repo_name]])
+            self.user_perms = set(
+                [self.user_perms['repositories'][self.repo_name]]
+            )
         except KeyError:
             return False
         self.granted_for = self.repo_name
@@ -580,8 +730,9 @@
             self.repo_name = get_repo_slug(request)
 
         try:
-            self.user_perms = set([self.user_perms['reposi'
-                                                   'tories'][self.repo_name]])
+            self.user_perms = set(
+                [self.user_perms['repositories'][self.repo_name]]
+            )
         except KeyError:
             return False
         self.granted_for = self.repo_name
@@ -590,6 +741,42 @@
         return False
 
 
+class HasReposGroupPermissionAny(PermsFunction):
+    def __call__(self, group_name=None, check_Location=''):
+        self.group_name = group_name
+        return super(HasReposGroupPermissionAny, self).__call__(check_Location)
+
+    def check_permissions(self):
+        try:
+            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):
+            return True
+        return False
+
+
+class HasReposGroupPermissionAll(PermsFunction):
+    def __call__(self, group_name=None, check_Location=''):
+        self.group_name = group_name
+        return super(HasReposGroupPermissionAny, self).__call__(check_Location)
+
+    def check_permissions(self):
+        try:
+            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):
+            return True
+        return False
+
+
 #==============================================================================
 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
 #==============================================================================
--- a/rhodecode/lib/auth_ldap.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/auth_ldap.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Created on Nov 17, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -43,8 +43,7 @@
     def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
                  tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
                  ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
-                 search_scope='SUBTREE',
-                 attr_login='uid'):
+                 search_scope='SUBTREE', attr_login='uid'):
         self.ldap_version = ldap_version
         ldap_server_type = 'ldap'
 
@@ -53,14 +52,14 @@
         if self.TLS_KIND == 'LDAPS':
             port = port or 689
             ldap_server_type = ldap_server_type + 's'
-        
+
         OPT_X_TLS_DEMAND = 2
-        self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, 
+        self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
                                    OPT_X_TLS_DEMAND)
         self.LDAP_SERVER_ADDRESS = server
         self.LDAP_SERVER_PORT = port
 
-        #USE FOR READ ONLY BIND TO LDAP SERVER
+        # USE FOR READ ONLY BIND TO LDAP SERVER
         self.LDAP_BIND_DN = bind_dn
         self.LDAP_BIND_PASS = bind_pass
 
@@ -74,7 +73,8 @@
         self.attr_login = attr_login
 
     def authenticate_ldap(self, username, password):
-        """Authenticate a user via LDAP and return his/her LDAP properties.
+        """
+        Authenticate a user via LDAP and return his/her LDAP properties.
 
         Raises AuthenticationError if the credentials are rejected, or
         EnvironmentError if the LDAP server can't be reached.
@@ -87,11 +87,15 @@
 
         uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
 
+        if not password:
+            log.debug("Attempt to authenticate LDAP user "
+                      "with blank password rejected.")
+            raise LdapPasswordError()
         if "," in username:
             raise LdapUsernameError("invalid character in username: ,")
         try:
-            if hasattr(ldap,'OPT_X_TLS_CACERTDIR'):
-                ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, 
+            if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
+                ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
                                 '/etc/openldap/cacerts')
             ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
             ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
@@ -112,12 +116,12 @@
             if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
                 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
 
-            filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
+            filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
                                      username)
-            log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
-                      filt, self.LDAP_SERVER)
+            log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
+                      filter_, self.LDAP_SERVER)
             lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
-                                           filt)
+                                           filter_)
 
             if not lobjects:
                 raise ldap.NO_SUCH_OBJECT()
@@ -127,24 +131,28 @@
                     continue
 
                 try:
+                    log.debug('Trying simple bind with %s' % dn)
                     server.simple_bind_s(dn, password)
                     attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
                                                 '(objectClass=*)')[0][1]
                     break
 
-                except ldap.INVALID_CREDENTIALS, e:
-                    log.debug("LDAP rejected password for user '%s' (%s): %s",
-                              uid, username, dn)
+                except ldap.INVALID_CREDENTIALS:
+                    log.debug(
+                        "LDAP rejected password for user '%s' (%s): %s" % (
+                            uid, username, dn
+                        )
+                    )
 
             else:
                 log.debug("No matching LDAP objects for authentication "
                           "of '%s' (%s)", uid, username)
                 raise LdapPasswordError()
 
-        except ldap.NO_SUCH_OBJECT, e:
-            log.debug("LDAP says no such user '%s' (%s)", uid, username)
+        except ldap.NO_SUCH_OBJECT:
+            log.debug("LDAP says no such user '%s' (%s)" % (uid, username))
             raise LdapUsernameError()
-        except ldap.SERVER_DOWN, e:
+        except ldap.SERVER_DOWN:
             raise LdapConnectionError("LDAP can't access "
                                       "authentication server")
 
--- a/rhodecode/lib/backup_manager.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/backup_manager.py	Sun Feb 26 17:25:09 2012 +0200
@@ -3,11 +3,12 @@
     rhodecode.lib.backup_manager
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    Mercurial repositories backup manager, it allows to backups all 
+    Mercurial repositories backup manager, it allows to backups all
     repositories and send it to backup server using RSA key via ssh.
 
     :created_on: Feb 28, 2010
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
--- a/rhodecode/lib/base.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/base.py	Sun Feb 26 17:25:09 2012 +0200
@@ -3,35 +3,125 @@
 Provides the BaseController class for subclassing.
 """
 import logging
+import time
+import traceback
+
+from paste.auth.basic import AuthBasicAuthenticator
 
 from pylons import config, tmpl_context as c, request, session, url
 from pylons.controllers import WSGIController
 from pylons.controllers.util import redirect
 from pylons.templating import render_mako as render
 
-from rhodecode import __version__
-from rhodecode.lib import str2bool
-from rhodecode.lib.auth import AuthUser
-from rhodecode.lib.utils import get_repo_slug
+from rhodecode import __version__, BACKENDS
+
+from rhodecode.lib 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
 from rhodecode.model import meta
+
+from rhodecode.model.db import Repository
+from rhodecode.model.notification import NotificationModel
 from rhodecode.model.scm import ScmModel
-from rhodecode import BACKENDS
-from rhodecode.model.db import Repository
 
 log = logging.getLogger(__name__)
 
+
+class BaseVCSController(object):
+
+    def __init__(self, application, config):
+        self.application = application
+        self.config = config
+        # base path of repo locations
+        self.basepath = self.config['base_path']
+        #authenticate this mercurial request using authfunc
+        self.authenticate = AuthBasicAuthenticator('', authfunc)
+        self.ipaddr = '0.0.0.0'
+
+    def _handle_request(self, environ, start_response):
+        raise NotImplementedError()
+
+    def _get_by_id(self, repo_name):
+        """
+        Get's a special pattern _<ID> from clone url and tries to replace it
+        with a repository_name for support of _<ID> non changable urls
+
+        :param repo_name:
+        """
+        try:
+            data = repo_name.split('/')
+            if len(data) >= 2:
+                by_id = data[1].split('_')
+                if len(by_id) == 2 and by_id[1].isdigit():
+                    _repo_name = Repository.get(by_id[1]).repo_name
+                    data[1] = _repo_name
+        except:
+            log.debug('Failed to extract repo_name from id %s' % (
+                      traceback.format_exc()
+                      )
+            )
+
+        return '/'.join(data)
+
+    def _invalidate_cache(self, repo_name):
+        """
+        Set's cache for this repository for invalidation on next access
+
+        :param repo_name: full repo name, also a cache key
+        """
+        invalidate_cache('get_repo_cached_%s' % repo_name)
+
+    def _check_permission(self, action, user, repo_name):
+        """
+        Checks permissions using action (push/pull) user and repository
+        name
+
+        :param action: push or pull action
+        :param user: user instance
+        :param repo_name: repository name
+        """
+        if action == 'push':
+            if not HasPermissionAnyMiddleware('repository.write',
+                                              'repository.admin')(user,
+                                                                  repo_name):
+                return False
+
+        else:
+            #any other action need at least read permission
+            if not HasPermissionAnyMiddleware('repository.read',
+                                              'repository.write',
+                                              'repository.admin')(user,
+                                                                  repo_name):
+                return False
+
+        return True
+
+    def __call__(self, environ, start_response):
+        start = time.time()
+        try:
+            return self._handle_request(environ, start_response)
+        finally:
+            log = logging.getLogger('rhodecode.' + self.__class__.__name__)
+            log.debug('Request time: %.3fs' % (time.time() - start))
+            meta.Session.remove()
+
+
 class BaseController(WSGIController):
 
     def __before__(self):
         c.rhodecode_version = __version__
+        c.rhodecode_instanceid = config.get('instance_id')
         c.rhodecode_name = config.get('rhodecode_title')
         c.use_gravatar = str2bool(config.get('use_gravatar'))
         c.ga_code = config.get('rhodecode_ga_code')
         c.repo_name = get_repo_slug(request)
         c.backends = BACKENDS.keys()
+        c.unread_notifications = NotificationModel()\
+                        .get_unread_cnt_for_user(c.rhodecode_user.user_id)
         self.cut_off_limit = int(config.get('cut_off_limit'))
 
-        self.sa = meta.Session()
+        self.sa = meta.Session
         self.scm_model = ScmModel(self.sa)
 
     def __call__(self, environ, start_response):
@@ -39,18 +129,30 @@
         # WSGIController.__call__ dispatches to the Controller method
         # the request is routed to. This routing information is
         # available in environ['pylons.routes_dict']
+        start = time.time()
         try:
-            # putting this here makes sure that we update permissions each time
+            # make sure that we update permissions each time we call controller
             api_key = request.GET.get('api_key')
-            user_id = getattr(session.get('rhodecode_user'), 'user_id', None)
-            self.rhodecode_user = c.rhodecode_user = AuthUser(user_id, api_key)
-            self.rhodecode_user.set_authenticated(
-                                        getattr(session.get('rhodecode_user'),
-                                       'is_authenticated', False))
-            session['rhodecode_user'] = self.rhodecode_user
-            session.save()
+            cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
+            user_id = cookie_store.get('user_id', None)
+            username = get_container_username(environ, config)
+
+            auth_user = AuthUser(user_id, api_key, username)
+            request.user = auth_user
+            self.rhodecode_user = c.rhodecode_user = auth_user
+            if not self.rhodecode_user.is_authenticated and \
+                       self.rhodecode_user.user_id is not None:
+                self.rhodecode_user.set_authenticated(
+                    cookie_store.get('is_authenticated')
+                )
+            log.info('User: %s accessed %s' % (
+                auth_user, safe_unicode(environ.get('PATH_INFO')))
+            )
             return WSGIController.__call__(self, environ, start_response)
         finally:
+            log.info('Request to %s time: %.3fs' % (
+                safe_unicode(environ.get('PATH_INFO')), time.time() - start)
+            )
             meta.Session.remove()
 
 
@@ -80,4 +182,3 @@
 
             c.repository_followers = self.scm_model.get_followers(c.repo_name)
             c.repository_forks = self.scm_model.get_forks(c.repo_name)
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/caching_query.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,299 @@
+"""caching_query.py
+
+Represent persistence structures which allow the usage of
+Beaker caching with SQLAlchemy.
+
+The three new concepts introduced here are:
+
+ * CachingQuery - a Query subclass that caches and
+   retrieves results in/from Beaker.
+ * FromCache - a query option that establishes caching
+   parameters on a Query
+ * RelationshipCache - a variant of FromCache which is specific
+   to a query invoked during a lazy load.
+ * _params_from_query - extracts value parameters from
+   a Query.
+
+The rest of what's here are standard SQLAlchemy and
+Beaker constructs.
+
+"""
+import beaker
+from beaker.exceptions import BeakerException
+
+from sqlalchemy.orm.interfaces import MapperOption
+from sqlalchemy.orm.query import Query
+from sqlalchemy.sql import visitors
+
+
+class CachingQuery(Query):
+    """A Query subclass which optionally loads full results from a Beaker
+    cache region.
+
+    The CachingQuery stores additional state that allows it to consult
+    a Beaker cache before accessing the database:
+
+    * A "region", which is a cache region argument passed to a
+      Beaker CacheManager, specifies a particular cache configuration
+      (including backend implementation, expiration times, etc.)
+    * A "namespace", which is a qualifying name that identifies a
+      group of keys within the cache.  A query that filters on a name
+      might use the name "by_name", a query that filters on a date range
+      to a joined table might use the name "related_date_range".
+
+    When the above state is present, a Beaker cache is retrieved.
+
+    The "namespace" name is first concatenated with
+    a string composed of the individual entities and columns the Query
+    requests, i.e. such as ``Query(User.id, User.name)``.
+
+    The Beaker cache is then loaded from the cache manager based
+    on the region and composed namespace.  The key within the cache
+    itself is then constructed against the bind parameters specified
+    by this query, which are usually literals defined in the
+    WHERE clause.
+
+    The FromCache and RelationshipCache mapper options below represent
+    the "public" method of configuring this state upon the CachingQuery.
+
+    """
+
+    def __init__(self, manager, *args, **kw):
+        self.cache_manager = manager
+        Query.__init__(self, *args, **kw)
+
+    def __iter__(self):
+        """override __iter__ to pull results from Beaker
+           if particular attributes have been configured.
+
+           Note that this approach does *not* detach the loaded objects from
+           the current session. If the cache backend is an in-process cache
+           (like "memory") and lives beyond the scope of the current session's
+           transaction, those objects may be expired. The method here can be
+           modified to first expunge() each loaded item from the current
+           session before returning the list of items, so that the items
+           in the cache are not the same ones in the current Session.
+
+        """
+        if hasattr(self, '_cache_parameters'):
+            return self.get_value(createfunc=lambda:
+                                  list(Query.__iter__(self)))
+        else:
+            return Query.__iter__(self)
+
+    def invalidate(self):
+        """Invalidate the value represented by this Query."""
+
+        cache, cache_key = _get_cache_parameters(self)
+        cache.remove(cache_key)
+
+    def get_value(self, merge=True, createfunc=None):
+        """Return the value from the cache for this query.
+
+        Raise KeyError if no value present and no
+        createfunc specified.
+
+        """
+        cache, cache_key = _get_cache_parameters(self)
+        ret = cache.get_value(cache_key, createfunc=createfunc)
+        if merge:
+            ret = self.merge_result(ret, load=False)
+        return ret
+
+    def set_value(self, value):
+        """Set the value in the cache for this query."""
+
+        cache, cache_key = _get_cache_parameters(self)
+        cache.put(cache_key, value)
+
+
+def query_callable(manager, query_cls=CachingQuery):
+    def query(*arg, **kw):
+        return query_cls(manager, *arg, **kw)
+    return query
+
+
+def get_cache_region(name, region):
+    if region not in beaker.cache.cache_regions:
+        raise BeakerException('Cache region `%s` not configured '
+            'Check if proper cache settings are in the .ini files' % region)
+    kw = beaker.cache.cache_regions[region]
+    return beaker.cache.Cache._get_cache(name, kw)
+
+
+def _get_cache_parameters(query):
+    """For a query with cache_region and cache_namespace configured,
+    return the correspoinding Cache instance and cache key, based
+    on this query's current criterion and parameter values.
+
+    """
+    if not hasattr(query, '_cache_parameters'):
+        raise ValueError("This Query does not have caching "
+                         "parameters configured.")
+
+    region, namespace, cache_key = query._cache_parameters
+
+    namespace = _namespace_from_query(namespace, query)
+
+    if cache_key is None:
+        # cache key - the value arguments from this query's parameters.
+        args = [str(x) for x in _params_from_query(query)]
+        args.extend(filter(lambda k:k not in ['None', None, u'None'],
+                           [str(query._limit), str(query._offset)]))
+        cache_key = " ".join(args)
+
+    if cache_key is None:
+        raise Exception('Cache key cannot be None')
+
+    # get cache
+    #cache = query.cache_manager.get_cache_region(namespace, region)
+    cache = get_cache_region(namespace, region)
+    # optional - hash the cache_key too for consistent length
+    # import uuid
+    # cache_key= str(uuid.uuid5(uuid.NAMESPACE_DNS, cache_key))
+
+    return cache, cache_key
+
+
+def _namespace_from_query(namespace, query):
+    # cache namespace - the token handed in by the
+    # option + class we're querying against
+    namespace = " ".join([namespace] + [str(x) for x in query._entities])
+
+    # memcached wants this
+    namespace = namespace.replace(' ', '_')
+
+    return namespace
+
+
+def _set_cache_parameters(query, region, namespace, cache_key):
+
+    if hasattr(query, '_cache_parameters'):
+        region, namespace, cache_key = query._cache_parameters
+        raise ValueError("This query is already configured "
+                        "for region %r namespace %r" %
+                        (region, namespace)
+                    )
+    query._cache_parameters = region, namespace, cache_key
+
+
+class FromCache(MapperOption):
+    """Specifies that a Query should load results from a cache."""
+
+    propagate_to_loaders = False
+
+    def __init__(self, region, namespace, cache_key=None):
+        """Construct a new FromCache.
+
+        :param region: the cache region.  Should be a
+        region configured in the Beaker CacheManager.
+
+        :param namespace: the cache namespace.  Should
+        be a name uniquely describing the target Query's
+        lexical structure.
+
+        :param cache_key: optional.  A string cache key
+        that will serve as the key to the query.   Use this
+        if your query has a huge amount of parameters (such
+        as when using in_()) which correspond more simply to
+        some other identifier.
+
+        """
+        self.region = region
+        self.namespace = namespace
+        self.cache_key = cache_key
+
+    def process_query(self, query):
+        """Process a Query during normal loading operation."""
+
+        _set_cache_parameters(query, self.region, self.namespace,
+                              self.cache_key)
+
+
+class RelationshipCache(MapperOption):
+    """Specifies that a Query as called within a "lazy load"
+       should load results from a cache."""
+
+    propagate_to_loaders = True
+
+    def __init__(self, region, namespace, attribute):
+        """Construct a new RelationshipCache.
+
+        :param region: the cache region.  Should be a
+        region configured in the Beaker CacheManager.
+
+        :param namespace: the cache namespace.  Should
+        be a name uniquely describing the target Query's
+        lexical structure.
+
+        :param attribute: A Class.attribute which
+        indicates a particular class relationship() whose
+        lazy loader should be pulled from the cache.
+
+        """
+        self.region = region
+        self.namespace = namespace
+        self._relationship_options = {
+            (attribute.property.parent.class_, attribute.property.key): self
+        }
+
+    def process_query_conditionally(self, query):
+        """Process a Query that is used within a lazy loader.
+
+        (the process_query_conditionally() method is a SQLAlchemy
+        hook invoked only within lazyload.)
+
+        """
+        if query._current_path:
+            mapper, key = query._current_path[-2:]
+
+            for cls in mapper.class_.__mro__:
+                if (cls, key) in self._relationship_options:
+                    relationship_option = \
+                        self._relationship_options[(cls, key)]
+                    _set_cache_parameters(
+                            query,
+                            relationship_option.region,
+                            relationship_option.namespace,
+                            None)
+
+    def and_(self, option):
+        """Chain another RelationshipCache option to this one.
+
+        While many RelationshipCache objects can be specified on a single
+        Query separately, chaining them together allows for a more efficient
+        lookup during load.
+
+        """
+        self._relationship_options.update(option._relationship_options)
+        return self
+
+
+def _params_from_query(query):
+    """Pull the bind parameter values from a query.
+
+    This takes into account any scalar attribute bindparam set up.
+
+    E.g. params_from_query(query.filter(Cls.foo==5).filter(Cls.bar==7)))
+    would return [5, 7].
+
+    """
+    v = []
+    def visit_bindparam(bind):
+
+        if bind.key in query._params:
+            value = query._params[bind.key]
+        elif bind.callable:
+            # lazyloader may dig a callable in here, intended
+            # to late-evaluate params after autoflush is called.
+            # convert to a scalar value.
+            value = bind.callable()
+        else:
+            value = bind.value
+
+        v.append(value)
+    if query._criterion is not None:
+        visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam})
+    for f in query._from_obj:
+        visitors.traverse(f, {}, {'bindparam':visit_bindparam})
+    return v
--- a/rhodecode/lib/celerylib/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/celerylib/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -34,8 +34,8 @@
 from hashlib import md5
 from decorator import decorator
 
-from vcs.utils.lazy import LazyProperty
-
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode import CELERY_ON
 from rhodecode.lib import str2bool, safe_str
 from rhodecode.lib.pidlock import DaemonLock, LockHeld
 from rhodecode.model import init_model
@@ -48,11 +48,6 @@
 
 log = logging.getLogger(__name__)
 
-try:
-    CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
-except KeyError:
-    CELERY_ON = False
-
 
 class ResultWrapper(object):
     def __init__(self, task):
@@ -67,7 +62,7 @@
     if CELERY_ON:
         try:
             t = task.apply_async(args=args, kwargs=kwargs)
-            log.info('running task %s:%s', t.task_id, task)
+            log.info('running task %s:%s' % (t.task_id, task))
             return t
 
         except socket.error, e:
@@ -80,7 +75,7 @@
         except Exception, e:
             log.error(traceback.format_exc())
 
-    log.debug('executing task %s in sync mode', task)
+    log.debug('executing task %s in sync mode' % task)
     return ResultWrapper(task(*args, **kwargs))
 
 
@@ -100,7 +95,7 @@
         lockkey = __get_lockkey(func, *fargs, **fkwargs)
         lockkey_path = config['here']
 
-        log.info('running task with lockkey %s', lockkey)
+        log.info('running task with lockkey %s' % lockkey)
         try:
             l = DaemonLock(file_=jn(lockkey_path, lockkey))
             ret = func(*fargs, **fkwargs)
--- a/rhodecode/lib/celerylib/tasks.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/celerylib/tasks.py	Sun Feb 26 17:25:09 2012 +0200
@@ -28,7 +28,7 @@
 import os
 import traceback
 import logging
-from os.path import dirname as dn, join as jn
+from os.path import join as jn
 
 from time import mktime
 from operator import itemgetter
@@ -37,69 +37,76 @@
 from pylons import config, url
 from pylons.i18n.translation import _
 
+from rhodecode.lib.vcs import get_backend
+
+from rhodecode import CELERY_ON
 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
-from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
-    __get_lockkey, LockHeld, DaemonLock, get_session, dbsession
+from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
+    str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
 from rhodecode.lib.helpers import person
-from rhodecode.lib.smtp_mailer import SmtpMailer
-from rhodecode.lib.utils import add_cache
+from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
+from rhodecode.lib.utils import add_cache, action_logger
 from rhodecode.lib.compat import json, OrderedDict
 
-from rhodecode.model.db import RhodeCodeUi, Statistics, Repository, User
+from rhodecode.model.db import Statistics, Repository, User
 
-from vcs.backends import get_repo
-from vcs import get_backend
 
 add_cache(config)
 
 __all__ = ['whoosh_index', 'get_commits_stats',
            'reset_user_password', 'send_email']
 
-CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
-
 
-def get_repos_path():
-    sa = get_session()
-    q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
-    return q.ui_value
+def get_logger(cls):
+    if CELERY_ON:
+        try:
+            log = cls.get_logger()
+        except:
+            log = logging.getLogger(__name__)
+    else:
+        log = logging.getLogger(__name__)
+
+    return log
 
 
 @task(ignore_result=True)
 @locked_task
 @dbsession
 def whoosh_index(repo_location, full_index):
-    #log = whoosh_index.get_logger()
     from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
+    log = whoosh_index.get_logger(whoosh_index)
+    DBS = get_session()
+
     index_location = config['index_dir']
     WhooshIndexingDaemon(index_location=index_location,
-                         repo_location=repo_location, sa=get_session())\
+                         repo_location=repo_location, sa=DBS)\
                          .run(full_index=full_index)
 
 
 @task(ignore_result=True)
 @dbsession
 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
-    try:
-        log = get_commits_stats.get_logger()
-    except:
-        log = logging.getLogger(__name__)
-
+    log = get_logger(get_commits_stats)
+    DBS = get_session()
     lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
                             ts_max_y)
     lockkey_path = config['here']
 
-    log.info('running task with lockkey %s', lockkey)
+    log.info('running task with lockkey %s' % lockkey)
+
     try:
-        sa = get_session()
         lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
 
-        # for js data compatibilty cleans the key for person from '
+        # for js data compatibility cleans the key for person from '
         akc = lambda k: person(k).replace('"', "")
 
         co_day_auth_aggr = {}
         commits_by_day_aggregate = {}
-        repos_path = get_repos_path()
-        repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
+        repo = Repository.get_by_repo_name(repo_name)
+        if repo is None:
+            return True
+
+        repo = repo.scm_instance
         repo_size = repo.count()
         # return if repo have no revisions
         if repo_size < 1:
@@ -112,9 +119,9 @@
         last_cs = None
         timegetter = itemgetter('time')
 
-        dbrepo = sa.query(Repository)\
+        dbrepo = DBS.query(Repository)\
             .filter(Repository.repo_name == repo_name).scalar()
-        cur_stats = sa.query(Statistics)\
+        cur_stats = DBS.query(Statistics)\
             .filter(Statistics.repository == dbrepo).scalar()
 
         if cur_stats is not None:
@@ -132,7 +139,7 @@
                                         cur_stats.commit_activity_combined))
             co_day_auth_aggr = json.loads(cur_stats.commit_activity)
 
-        log.debug('starting parsing %s', parse_limit)
+        log.debug('starting parsing %s' % parse_limit)
         lmktime = mktime
 
         last_rev = last_rev + 1 if last_rev >= 0 else 0
@@ -207,9 +214,9 @@
         stats.commit_activity = json.dumps(co_day_auth_aggr)
         stats.commit_activity_combined = json.dumps(overview_data)
 
-        log.debug('last revison %s', last_rev)
+        log.debug('last revison %s' % last_rev)
         leftovers = len(repo.revisions[last_rev:])
-        log.debug('revisions to parse %s', leftovers)
+        log.debug('revisions to parse %s' % leftovers)
 
         if last_rev == 0 or leftovers < parse_limit:
             log.debug('getting code trending stats')
@@ -218,18 +225,18 @@
         try:
             stats.repository = dbrepo
             stats.stat_on_revision = last_cs.revision if last_cs else 0
-            sa.add(stats)
-            sa.commit()
+            DBS.add(stats)
+            DBS.commit()
         except:
             log.error(traceback.format_exc())
-            sa.rollback()
+            DBS.rollback()
             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
@@ -240,38 +247,28 @@
 @task(ignore_result=True)
 @dbsession
 def send_password_link(user_email):
-    try:
-        log = reset_user_password.get_logger()
-    except:
-        log = logging.getLogger(__name__)
+    from rhodecode.model.notification import EmailNotificationModel
 
-    from rhodecode.lib import auth
+    log = get_logger(send_password_link)
+    DBS = get_session()
 
     try:
-        sa = get_session()
-        user = sa.query(User).filter(User.email == user_email).scalar()
-
+        user = User.get_by_email(user_email)
         if user:
+            log.debug('password reset user found %s' % user)
             link = url('reset_password_confirmation', key=user.api_key,
                        qualified=True)
-            tmpl = """
-Hello %s
-
-We received a request to create a new password for your account.
-
-You can generate it by clicking following URL:
-
-%s
-
-If you didn't request new password please ignore this email.
-            """
+            reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
+            body = EmailNotificationModel().get_email_tmpl(reg_type,
+                                                **{'user':user.short_contact,
+                                                   'reset_url':link})
+            log.debug('sending email')
             run_task(send_email, user_email,
-                     "RhodeCode password reset link",
-                     tmpl % (user.short_contact, link))
-            log.info('send new password mail to %s', user_email)
-
+                     _("password reset link"), body)
+            log.info('send new password mail to %s' % user_email)
+        else:
+            log.debug("password reset email %s not found" % user_email)
     except:
-        log.error('Failed to update user password')
         log.error(traceback.format_exc())
         return False
 
@@ -280,36 +277,32 @@
 @task(ignore_result=True)
 @dbsession
 def reset_user_password(user_email):
-    try:
-        log = reset_user_password.get_logger()
-    except:
-        log = logging.getLogger(__name__)
+    from rhodecode.lib import auth
 
-    from rhodecode.lib import auth
+    log = get_logger(reset_user_password)
+    DBS = get_session()
 
     try:
         try:
-            sa = get_session()
-            user = sa.query(User).filter(User.email == user_email).scalar()
+            user = User.get_by_email(user_email)
             new_passwd = auth.PasswordGenerator().gen_password(8,
                              auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
             if user:
                 user.password = auth.get_crypt_password(new_passwd)
                 user.api_key = auth.generate_api_key(user.username)
-                sa.add(user)
-                sa.commit()
-                log.info('change password for %s', user_email)
+                DBS.add(user)
+                DBS.commit()
+                log.info('change password for %s' % user_email)
             if new_passwd is None:
                 raise Exception('unable to generate new password')
-
         except:
             log.error(traceback.format_exc())
-            sa.rollback()
+            DBS.rollback()
 
         run_task(send_email, user_email,
-                 "Your new RhodeCode password",
+                 'Your new password',
                  'Your new RhodeCode password:%s' % (new_passwd))
-        log.info('send new password mail to %s', user_email)
+        log.info('send new password mail to %s' % user_email)
 
     except:
         log.error('Failed to update user password')
@@ -320,7 +313,7 @@
 
 @task(ignore_result=True)
 @dbsession
-def send_email(recipients, subject, body):
+def send_email(recipients, subject, body, html_body=''):
     """
     Sends an email with defined parameters from the .ini files.
 
@@ -328,23 +321,20 @@
         address from field 'email_to' is used instead
     :param subject: subject of the mail
     :param body: body of the mail
+    :param html_body: html version of body
     """
-    try:
-        log = send_email.get_logger()
-    except:
-        log = logging.getLogger(__name__)
-    
-    sa = get_session()
+    log = get_logger(send_email)
+    DBS = get_session()
+
     email_config = config
-
+    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 sa.query(User).filter(User.admin==True).all()
-        ]
+        admins = [u.email for u in User.query()
+                  .filter(User.admin == True).all()]
         recipients = [email_config.get('email_to')] + admins
 
-    mail_from = email_config.get('app_email_from')
+    mail_from = email_config.get('app_email_from', 'RhodeCode')
     user = email_config.get('smtp_username')
     passwd = email_config.get('smtp_password')
     mail_server = email_config.get('smtp_server')
@@ -355,9 +345,9 @@
     smtp_auth = email_config.get('smtp_auth')
 
     try:
-        m = SmtpMailer(mail_from, user, passwd, mail_server,smtp_auth,
+        m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
                        mail_port, ssl, tls, debug=debug)
-        m.send(recipients, subject, body)
+        m.send(recipients, subject, body, html_body)
     except:
         log.error('Mail sending failed')
         log.error(traceback.format_exc())
@@ -368,29 +358,45 @@
 @task(ignore_result=True)
 @dbsession
 def create_repo_fork(form_data, cur_user):
+    """
+    Creates a fork of repository using interval VCS methods
+
+    :param form_data:
+    :param cur_user:
+    """
     from rhodecode.model.repo import RepoModel
 
-    try:
-        log = create_repo_fork.get_logger()
-    except:
-        log = logging.getLogger(__name__)
+    log = get_logger(create_repo_fork)
+    DBS = get_session()
+
+    base_path = Repository.base_path()
+
+    RepoModel(DBS).create(form_data, cur_user, just_db=True, fork=True)
+
+    alias = form_data['repo_type']
+    org_repo_name = form_data['org_path']
+    fork_name = form_data['repo_name_full']
+    update_after_clone = form_data['update_after_clone']
+    source_repo_path = os.path.join(base_path, org_repo_name)
+    destination_fork_path = os.path.join(base_path, fork_name)
 
-    repo_model = RepoModel(get_session())
-    repo_model.create(form_data, cur_user, just_db=True, fork=True)
-    repo_name = form_data['repo_name']
-    repos_path = get_repos_path()
-    repo_path = os.path.join(repos_path, repo_name)
-    repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
-    alias = form_data['repo_type']
+    log.info('creating fork of %s as %s', source_repo_path,
+             destination_fork_path)
+    backend = get_backend(alias)
+    backend(safe_str(destination_fork_path), create=True,
+            src_url=safe_str(source_repo_path),
+            update_after_clone=update_after_clone)
+    action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
+                   org_repo_name, '', DBS)
 
-    log.info('creating repo fork %s as %s', repo_name, repo_path)
-    backend = get_backend(alias)
-    backend(str(repo_fork_path), create=True, src_url=str(repo_path))
-
+    action_logger(cur_user, 'user_created_fork:%s' % fork_name,
+                   fork_name, '', DBS)
+    # finally commit at latest possible stage
+    DBS.commit()
 
 def __get_codes_stats(repo_name):
-    repos_path = get_repos_path()
-    repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
+    repo = Repository.get_by_repo_name(repo_name).scm_instance
+
     tip = repo.get_changeset()
     code_stats = {}
 
--- a/rhodecode/lib/celerypylons/commands.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/celerypylons/commands.py	Sun Feb 26 17:25:09 2012 +0200
@@ -1,7 +1,10 @@
+import rhodecode
 from rhodecode.lib.utils import BasePasterCommand, Command
 from celery.app import app_or_default
 from celery.bin import camqadm, celerybeat, celeryd, celeryev
 
+from rhodecode.lib import str2bool
+
 __all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand',
            'CAMQPAdminCommand', 'CeleryEventCommand']
 
@@ -26,6 +29,16 @@
             self.parser.add_option(x)
 
     def command(self):
+        from pylons import config
+        try:
+            CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
+        except KeyError:
+            CELERY_ON = False
+
+        if CELERY_ON == False:
+            raise Exception('Please enable celery_on in .ini config '
+                            'file before running celeryd')
+        rhodecode.CELERY_ON = CELERY_ON
         cmd = self.celery_command(app_or_default())
         return cmd.run(**vars(self.options))
 
--- a/rhodecode/lib/compat.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/compat.py	Sun Feb 26 17:25:09 2012 +0200
@@ -4,11 +4,11 @@
     ~~~~~~~~~~~~~~~~~~~~
 
     Python backward compatibility functions and common libs
-    
-    
+
+
     :created_on: Oct 7, 2011
     :author: marcink
-    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>    
+    :copyright: (C) 2010-2010 Marcin Kuzminski <marcin@python-works.com>
     :license: GPLv3, see COPYING for more details.
 """
 # This program is free software: you can redistribute it and/or modify
@@ -87,10 +87,11 @@
 
 _nil = _Nil()
 
+
 class _odict(object):
     """Ordered dict data structure, with O(1) complexity for dict operations
     that modify one element.
-    
+
     Overwriting values doesn't change their original sequential order.
     """
 
@@ -146,7 +147,7 @@
         dict_impl = self._dict_impl()
         try:
             dict_impl.__getitem__(self, key)[1] = val
-        except KeyError, e:
+        except KeyError:
             new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
             dict_impl.__setitem__(self, key, new)
             if dict_impl.__getattribute__(self, 'lt') == _nil:
@@ -158,7 +159,7 @@
 
     def __delitem__(self, key):
         dict_impl = self._dict_impl()
-        pred, _ , succ = self._dict_impl().__getitem__(self, key)
+        pred, _, succ = self._dict_impl().__getitem__(self, key)
         if pred == _nil:
             dict_impl.__setattr__(self, 'lh', succ)
         else:
@@ -351,6 +352,7 @@
                        dict_impl.__getattribute__(self, 'lt'),
                        dict_impl.__repr__(self))
 
+
 class OrderedDict(_odict, dict):
 
     def _dict_impl(self):
--- a/rhodecode/lib/db_manage.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/db_manage.py	Sun Feb 26 17:25:09 2012 +0200
@@ -8,7 +8,7 @@
 
     :created_on: Apr 10, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -33,13 +33,15 @@
 from rhodecode import __dbversion__
 from rhodecode.model import meta
 
-from rhodecode.lib.auth import get_crypt_password, generate_api_key
+from rhodecode.model.user import UserModel
 from rhodecode.lib.utils import ask_ok
 from rhodecode.model import init_model
 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
-    RhodeCodeSettings, UserToPerm, DbMigrateVersion
+    RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\
+    UserRepoGroupToPerm
 
 from sqlalchemy.engine import create_engine
+from rhodecode.model.repos_group import ReposGroupModel
 
 log = logging.getLogger(__name__)
 
@@ -57,10 +59,11 @@
     def init_db(self):
         engine = create_engine(self.dburi, echo=self.log_sql)
         init_model(engine)
-        self.sa = meta.Session()
+        self.sa = meta.Session
 
     def create_tables(self, override=False):
-        """Create a auth database
+        """
+        Create a auth database
         """
 
         log.info("Any existing database is going to be destroyed")
@@ -75,23 +78,19 @@
 
         checkfirst = not override
         meta.Base.metadata.create_all(checkfirst=checkfirst)
-        log.info('Created tables for %s', self.dbname)
+        log.info('Created tables for %s' % self.dbname)
 
     def set_db_version(self):
-        try:
-            ver = DbMigrateVersion()
-            ver.version = __dbversion__
-            ver.repository_id = 'rhodecode_db_migrations'
-            ver.repository_path = 'versions'
-            self.sa.add(ver)
-            self.sa.commit()
-        except:
-            self.sa.rollback()
-            raise
-        log.info('db version set to: %s', __dbversion__)
+        ver = DbMigrateVersion()
+        ver.version = __dbversion__
+        ver.repository_id = 'rhodecode_db_migrations'
+        ver.repository_path = 'versions'
+        self.sa.add(ver)
+        log.info('db version set to: %s' % __dbversion__)
 
     def upgrade(self):
-        """Upgrades given database schema to given revision following
+        """
+        Upgrades given database schema to given revision following
         all needed steps, to perform the upgrade
 
         """
@@ -146,7 +145,7 @@
                 self.klass = klass
 
             def step_0(self):
-                #step 0 is the schema upgrade, and than follow proper upgrades
+                # step 0 is the schema upgrade, and than follow proper upgrades
                 print ('attempting to do database upgrade to version %s' \
                                 % __dbversion__)
                 api.upgrade(db_uri, repository_path, __dbversion__)
@@ -170,16 +169,26 @@
                 self.klass.fix_settings()
                 print ('Adding ldap defaults')
                 self.klass.create_ldap_options(skip_existing=True)
-                
+
+            def step_4(self):
+                print ('create permissions and fix groups')
+                self.klass.create_permissions()
+                self.klass.fixup_groups()
+
+            def step_5(self):
+                pass
+
         upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
 
-        #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
+        # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
         for step in upgrade_steps:
             print ('performing upgrade step %s' % step)
             getattr(UpgradeSteps(self), 'step_%s' % step)()
+            self.sa.commit()
 
     def fix_repo_paths(self):
-        """Fixes a old rhodecode version path into new one without a '*'
+        """
+        Fixes a old rhodecode version path into new one without a '*'
         """
 
         paths = self.sa.query(RhodeCodeUi)\
@@ -196,7 +205,8 @@
             raise
 
     def fix_default_user(self):
-        """Fixes a old default user with some 'nicer' default values,
+        """
+        Fixes a old default user with some 'nicer' default values,
         used mostly for anonymous access
         """
         def_user = self.sa.query(User)\
@@ -215,10 +225,11 @@
             raise
 
     def fix_settings(self):
-        """Fixes rhodecode settings adds ga_code key for google analytics
+        """
+        Fixes rhodecode settings adds ga_code key for google analytics
         """
 
-        hgsettings3 = RhodeCodeSettings('ga_code', '')
+        hgsettings3 = RhodeCodeSetting('ga_code', '')
 
         try:
             self.sa.add(hgsettings3)
@@ -258,18 +269,27 @@
             self.create_user(username, password, email, True)
         else:
             log.info('creating admin and regular test users')
-            self.create_user('test_admin', 'test12',
-                             'test_admin@mail.com', True)
-            self.create_user('test_regular', 'test12',
-                             'test_regular@mail.com', False)
-            self.create_user('test_regular2', 'test12',
-                             'test_regular2@mail.com', False)
+            from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
+            TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
+            TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
+            TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
+            TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
+
+            self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
+                             TEST_USER_ADMIN_EMAIL, True)
+
+            self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
+                             TEST_USER_REGULAR_EMAIL, False)
+
+            self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
+                             TEST_USER_REGULAR2_EMAIL, False)
 
     def create_ui_settings(self):
-        """Creates ui settings, fills out hooks
+        """
+        Creates ui settings, fills out hooks
         and disables dotencode
+        """
 
-        """
         #HOOKS
         hooks1_key = RhodeCodeUi.HOOK_UPDATE
         hooks1_ = self.sa.query(RhodeCodeUi)\
@@ -300,7 +320,7 @@
         hooks4.ui_key = RhodeCodeUi.HOOK_PULL
         hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
 
-        #For mercurial 1.7 set backward comapatibility with format
+        # For mercurial 1.7 set backward comapatibility with format
         dotencode_disable = RhodeCodeUi()
         dotencode_disable.ui_section = 'format'
         dotencode_disable.ui_key = 'dotencode'
@@ -312,39 +332,43 @@
         largefiles.ui_key = 'largefiles'
         largefiles.ui_value = ''
 
-        try:
-            self.sa.add(hooks1)
-            self.sa.add(hooks2)
-            self.sa.add(hooks3)
-            self.sa.add(hooks4)
-            self.sa.add(dotencode_disable)
-            self.sa.add(largefiles)
-            self.sa.commit()
-        except:
-            self.sa.rollback()
-            raise
+        self.sa.add(hooks1)
+        self.sa.add(hooks2)
+        self.sa.add(hooks3)
+        self.sa.add(hooks4)
+        self.sa.add(largefiles)
 
-    def create_ldap_options(self,skip_existing=False):
+    def create_ldap_options(self, skip_existing=False):
         """Creates ldap settings"""
 
-        try:
-            for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
-                        ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
-                        ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
-                        ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
-                        ('ldap_filter', ''), ('ldap_search_scope', ''),
-                        ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
-                        ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
+        for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
+                    ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
+                    ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
+                    ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
+                    ('ldap_filter', ''), ('ldap_search_scope', ''),
+                    ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
+                    ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
+
+            if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
+                log.debug('Skipping option %s' % k)
+                continue
+            setting = RhodeCodeSetting(k, v)
+            self.sa.add(setting)
 
-                if skip_existing and RhodeCodeSettings.get_by_name(k) != None:
-                    log.debug('Skipping option %s' % k)
-                    continue
-                setting = RhodeCodeSettings(k, v)
-                self.sa.add(setting)
-            self.sa.commit()
-        except:
-            self.sa.rollback()
-            raise
+    def fixup_groups(self):
+        def_usr = User.get_by_username('default')
+        for g in RepoGroup.query().all():
+            g.group_name = g.get_new_name(g.name)
+            self.sa.add(g)
+            # get default perm
+            default = UserRepoGroupToPerm.query()\
+                .filter(UserRepoGroupToPerm.group == g)\
+                .filter(UserRepoGroupToPerm.user == def_usr)\
+                .scalar()
+
+            if default is None:
+                log.debug('missing default permission for group %s adding' % g)
+                ReposGroupModel()._create_default_perms(g)
 
     def config_prompt(self, test_repo_path='', retries=3):
         if retries == 3:
@@ -359,16 +383,15 @@
             path = test_repo_path
         path_ok = True
 
-        #check proper dir
+        # check proper dir
         if not os.path.isdir(path):
             path_ok = False
-            log.error('Given path %s is not a valid directory', path)
+            log.error('Given path %s is not a valid directory' % path)
 
-        #check write access
+        # check write access
         if not os.access(path, os.W_OK) and path_ok:
             path_ok = False
-            log.error('No write permission to given path %s', path)
-
+            log.error('No write permission to given path %s' % path)
 
         if retries == 0:
             sys.exit('max retries reached')
@@ -408,85 +431,68 @@
         paths.ui_key = '/'
         paths.ui_value = path
 
-        hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
-        hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
-        hgsettings3 = RhodeCodeSettings('ga_code', '')
+        hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
+        hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
+        hgsettings3 = RhodeCodeSetting('ga_code', '')
 
-        try:
-            self.sa.add(web1)
-            self.sa.add(web2)
-            self.sa.add(web3)
-            self.sa.add(web4)
-            self.sa.add(paths)
-            self.sa.add(hgsettings1)
-            self.sa.add(hgsettings2)
-            self.sa.add(hgsettings3)
-
-            self.sa.commit()
-        except:
-            self.sa.rollback()
-            raise
+        self.sa.add(web1)
+        self.sa.add(web2)
+        self.sa.add(web3)
+        self.sa.add(web4)
+        self.sa.add(paths)
+        self.sa.add(hgsettings1)
+        self.sa.add(hgsettings2)
+        self.sa.add(hgsettings3)
 
         self.create_ldap_options()
 
         log.info('created ui config')
 
     def create_user(self, username, password, email='', admin=False):
-        log.info('creating administrator user %s', username)
-        
-        form_data = dict(username=username,
-                         password=password,
-                         active=True,
-                         admin=admin,
-                         name='RhodeCode',
-                         lastname='Admin',
-                         email=email)
-        User.create(form_data)
-
+        log.info('creating user %s' % username)
+        UserModel().create_or_update(username, password, email,
+                                     name='RhodeCode', lastname='Admin',
+                                     active=True, admin=admin)
 
     def create_default_user(self):
         log.info('creating default user')
-        #create default user for handling default permissions.
+        # create default user for handling default permissions.
+        UserModel().create_or_update(username='default',
+                              password=str(uuid.uuid1())[:8],
+                              email='anonymous@rhodecode.org',
+                              name='Anonymous', lastname='User')
 
-        form_data = dict(username='default',
-                         password=str(uuid.uuid1())[:8],
-                         active=False,
-                         admin=False,
-                         name='Anonymous',
-                         lastname='User',
-                         email='anonymous@rhodecode.org')
-        User.create(form_data)
-        
     def create_permissions(self):
-        #module.(access|create|change|delete)_[name]
-        #module.(read|write|owner)
-        perms = [('repository.none', 'Repository no access'),
-                 ('repository.read', 'Repository read access'),
-                 ('repository.write', 'Repository write access'),
-                 ('repository.admin', 'Repository admin access'),
-                 ('hg.admin', 'Hg Administrator'),
-                 ('hg.create.repository', 'Repository create'),
-                 ('hg.create.none', 'Repository creation disabled'),
-                 ('hg.register.none', 'Register disabled'),
-                 ('hg.register.manual_activate', 'Register new user with '
-                                                 'RhodeCode without manual'
-                                                 'activation'),
+        # module.(access|create|change|delete)_[name]
+        # module.(none|read|write|admin)
+        perms = [
+         ('repository.none', 'Repository no access'),
+         ('repository.read', 'Repository read access'),
+         ('repository.write', 'Repository write access'),
+         ('repository.admin', 'Repository admin access'),
 
-                 ('hg.register.auto_activate', 'Register new user with '
-                                               'RhodeCode without auto '
-                                               'activation'),
-                ]
+         ('group.none', 'Repositories Group no access'),
+         ('group.read', 'Repositories Group read access'),
+         ('group.write', 'Repositories Group write access'),
+         ('group.admin', 'Repositories Group admin access'),
+
+         ('hg.admin', 'Hg Administrator'),
+         ('hg.create.repository', 'Repository create'),
+         ('hg.create.none', 'Repository creation disabled'),
+         ('hg.register.none', 'Register disabled'),
+         ('hg.register.manual_activate', 'Register new user with RhodeCode '
+                                         'without manual activation'),
+
+         ('hg.register.auto_activate', 'Register new user with RhodeCode '
+                                        'without auto activation'),
+        ]
 
         for p in perms:
-            new_perm = Permission()
-            new_perm.permission_name = p[0]
-            new_perm.permission_longname = p[1]
-            try:
+            if not Permission.get_by_key(p[0]):
+                new_perm = Permission()
+                new_perm.permission_name = p[0]
+                new_perm.permission_longname = p[1]
                 self.sa.add(new_perm)
-                self.sa.commit()
-            except:
-                self.sa.rollback()
-                raise
 
     def populate_default_permissions(self):
         log.info('creating default user permissions')
@@ -512,11 +518,6 @@
         .filter(Permission.permission_name == 'repository.read')\
         .scalar()
 
-        try:
-            self.sa.add(reg_perm)
-            self.sa.add(create_repo_perm)
-            self.sa.add(default_repo_perm)
-            self.sa.commit()
-        except:
-            self.sa.rollback()
-            raise
+        self.sa.add(reg_perm)
+        self.sa.add(create_repo_perm)
+        self.sa.add(default_repo_perm)
--- a/rhodecode/lib/dbmigrate/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Dec 11, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
--- a/rhodecode/lib/dbmigrate/migrate/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -8,4 +8,4 @@
 from rhodecode.lib.dbmigrate.migrate.versioning import *
 from rhodecode.lib.dbmigrate.migrate.changeset import *
 
-__version__ = '0.7.2.dev'
\ No newline at end of file
+__version__ = '0.7.3.dev'
--- a/rhodecode/lib/dbmigrate/migrate/changeset/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/changeset/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -12,7 +12,7 @@
 
 warnings.simplefilter('always', DeprecationWarning)
 
-_sa_version = tuple(int(re.match("\d+", x).group(0)) 
+_sa_version = tuple(int(re.match("\d+", x).group(0))
                     for x in _sa_version.split("."))
 SQLA_06 = _sa_version >= (0, 6)
 SQLA_07 = _sa_version >= (0, 7)
--- a/rhodecode/lib/dbmigrate/migrate/changeset/ansisql.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/changeset/ansisql.py	Sun Feb 26 17:25:09 2012 +0200
@@ -17,23 +17,19 @@
                                Index)
 
 from rhodecode.lib.dbmigrate.migrate import exceptions
-from rhodecode.lib.dbmigrate.migrate.changeset import constraint, SQLA_06
+from rhodecode.lib.dbmigrate.migrate.changeset import constraint
 
-if not SQLA_06:
-    from sqlalchemy.sql.compiler import SchemaGenerator, SchemaDropper
-else:
-    from sqlalchemy.schema import AddConstraint, DropConstraint
-    from sqlalchemy.sql.compiler import DDLCompiler
-    SchemaGenerator = SchemaDropper = DDLCompiler
+from sqlalchemy.schema import AddConstraint, DropConstraint
+from sqlalchemy.sql.compiler import DDLCompiler
+SchemaGenerator = SchemaDropper = DDLCompiler
 
 
 class AlterTableVisitor(SchemaVisitor):
     """Common operations for ``ALTER TABLE`` statements."""
 
-    if SQLA_06:
-        # engine.Compiler looks for .statement
-        # when it spawns off a new compiler
-        statement = ClauseElement()
+    # engine.Compiler looks for .statement
+    # when it spawns off a new compiler
+    statement = ClauseElement()
 
     def append(self, s):
         """Append content to the SchemaIterator's query buffer."""
@@ -123,9 +119,8 @@
                                                    name=column.primary_key_name)
             cons.create()
 
-    if SQLA_06:
-        def add_foreignkey(self, fk):
-            self.connection.execute(AddConstraint(fk))
+    def add_foreignkey(self, fk):
+        self.connection.execute(AddConstraint(fk))
 
 class ANSIColumnDropper(AlterTableVisitor, SchemaDropper):
     """Extends ANSI SQL dropper for column dropping (``ALTER TABLE
@@ -232,10 +227,7 @@
 
     def _visit_column_type(self, table, column, delta):
         type_ = delta['type']
-        if SQLA_06:
-            type_text = str(type_.compile(dialect=self.dialect))
-        else:
-            type_text = type_.dialect_impl(self.dialect).get_col_spec()
+        type_text = str(type_.compile(dialect=self.dialect))
         self.append("TYPE %s" % type_text)
 
     def _visit_column_name(self, table, column, delta):
@@ -279,75 +271,17 @@
     def visit_migrate_unique_constraint(self, *p, **k):
         self._visit_constraint(*p, **k)
 
-if SQLA_06:
-    class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
-        def _visit_constraint(self, constraint):
-            constraint.name = self.get_constraint_name(constraint)
-            self.append(self.process(AddConstraint(constraint)))
-            self.execute()
-
-    class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
-        def _visit_constraint(self, constraint):
-            constraint.name = self.get_constraint_name(constraint)
-            self.append(self.process(DropConstraint(constraint, cascade=constraint.cascade)))
-            self.execute()
-
-else:
-    class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
-
-        def get_constraint_specification(self, cons, **kwargs):
-            """Constaint SQL generators.
-
-            We cannot use SA visitors because they append comma.
-            """
+class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
+    def _visit_constraint(self, constraint):
+        constraint.name = self.get_constraint_name(constraint)
+        self.append(self.process(AddConstraint(constraint)))
+        self.execute()
 
-            if isinstance(cons, PrimaryKeyConstraint):
-                if cons.name is not None:
-                    self.append("CONSTRAINT %s " % self.preparer.format_constraint(cons))
-                self.append("PRIMARY KEY ")
-                self.append("(%s)" % ', '.join(self.preparer.quote(c.name, c.quote)
-                                               for c in cons))
-                self.define_constraint_deferrability(cons)
-            elif isinstance(cons, ForeignKeyConstraint):
-                self.define_foreign_key(cons)
-            elif isinstance(cons, CheckConstraint):
-                if cons.name is not None:
-                    self.append("CONSTRAINT %s " %
-                                self.preparer.format_constraint(cons))
-                self.append("CHECK (%s)" % cons.sqltext)
-                self.define_constraint_deferrability(cons)
-            elif isinstance(cons, UniqueConstraint):
-                if cons.name is not None:
-                    self.append("CONSTRAINT %s " %
-                                self.preparer.format_constraint(cons))
-                self.append("UNIQUE (%s)" % \
-                    (', '.join(self.preparer.quote(c.name, c.quote) for c in cons)))
-                self.define_constraint_deferrability(cons)
-            else:
-                raise exceptions.InvalidConstraintError(cons)
-
-        def _visit_constraint(self, constraint):
-
-            table = self.start_alter_table(constraint)
-            constraint.name = self.get_constraint_name(constraint)
-            self.append("ADD ")
-            self.get_constraint_specification(constraint)
-            self.execute()
-
-
-    class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
-
-        def _visit_constraint(self, constraint):
-            self.start_alter_table(constraint)
-            self.append("DROP CONSTRAINT ")
-            constraint.name = self.get_constraint_name(constraint)
-            self.append(self.preparer.format_constraint(constraint))
-            if constraint.cascade:
-                self.cascade_constraint(constraint)
-            self.execute()
-
-        def cascade_constraint(self, constraint):
-            self.append(" CASCADE")
+class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
+    def _visit_constraint(self, constraint):
+        constraint.name = self.get_constraint_name(constraint)
+        self.append(self.process(DropConstraint(constraint, cascade=constraint.cascade)))
+        self.execute()
 
 
 class ANSIDialect(DefaultDialect):
--- a/rhodecode/lib/dbmigrate/migrate/changeset/constraint.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/changeset/constraint.py	Sun Feb 26 17:25:09 2012 +0200
@@ -4,7 +4,7 @@
 from sqlalchemy import schema
 
 from rhodecode.lib.dbmigrate.migrate.exceptions import *
-from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06
+
 
 class ConstraintChangeset(object):
     """Base class for Constraint classes."""
@@ -85,7 +85,6 @@
         if table is not None:
             self._set_parent(table)
 
-
     def autoname(self):
         """Mimic the database's automatic constraint names"""
         return "%s_pkey" % self.table.name
@@ -111,8 +110,9 @@
         table = kwargs.pop('table', table)
         refcolnames, reftable = self._normalize_columns(refcolumns,
                                                         table_name=True)
-        super(ForeignKeyConstraint, self).__init__(colnames, refcolnames, *args,
-                                                   **kwargs)
+        super(ForeignKeyConstraint, self).__init__(
+            colnames, refcolnames, *args,**kwargs
+        )
         if table is not None:
             self._set_parent(table)
 
@@ -165,8 +165,6 @@
         table = kwargs.pop('table', table)
         schema.CheckConstraint.__init__(self, sqltext, *args, **kwargs)
         if table is not None:
-            if not SQLA_06:
-                self.table = table
             self._set_parent(table)
         self.colnames = colnames
 
@@ -199,4 +197,4 @@
 
     def autoname(self):
         """Mimic the database's automatic constraint names"""
-        return "%s_%s_key" % (self.table.name, self.colnames[0])
+        return "%s_%s_key" % (self.table.name, '_'.join(self.colnames))
--- a/rhodecode/lib/dbmigrate/migrate/changeset/databases/firebird.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/firebird.py	Sun Feb 26 17:25:09 2012 +0200
@@ -4,13 +4,10 @@
 from sqlalchemy.databases import firebird as sa_base
 from sqlalchemy.schema import PrimaryKeyConstraint
 from rhodecode.lib.dbmigrate.migrate import exceptions
-from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
+from rhodecode.lib.dbmigrate.migrate.changeset import ansisql
 
 
-if SQLA_06:
-    FBSchemaGenerator = sa_base.FBDDLCompiler
-else:
-    FBSchemaGenerator = sa_base.FBSchemaGenerator
+FBSchemaGenerator = sa_base.FBDDLCompiler
 
 class FBColumnGenerator(FBSchemaGenerator, ansisql.ANSIColumnGenerator):
     """Firebird column generator implementation."""
@@ -41,10 +38,7 @@
                 # is deleted!
                 continue
 
-            if SQLA_06:
-                should_drop = column.name in cons.columns
-            else:
-                should_drop = cons.contains_column(column) and cons.name
+            should_drop = column.name in cons.columns
             if should_drop:
                 self.start_alter_table(column)
                 self.append("DROP CONSTRAINT ")
--- a/rhodecode/lib/dbmigrate/migrate/changeset/databases/mysql.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/mysql.py	Sun Feb 26 17:25:09 2012 +0200
@@ -6,13 +6,10 @@
 from sqlalchemy import types as sqltypes
 
 from rhodecode.lib.dbmigrate.migrate import exceptions
-from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
+from rhodecode.lib.dbmigrate.migrate.changeset import ansisql
 
 
-if not SQLA_06:
-    MySQLSchemaGenerator = sa_base.MySQLSchemaGenerator
-else:
-    MySQLSchemaGenerator = sa_base.MySQLDDLCompiler
+MySQLSchemaGenerator = sa_base.MySQLDDLCompiler
 
 class MySQLColumnGenerator(MySQLSchemaGenerator, ansisql.ANSIColumnGenerator):
     pass
@@ -53,37 +50,11 @@
 class MySQLConstraintGenerator(ansisql.ANSIConstraintGenerator):
     pass
 
-if SQLA_06:
-    class MySQLConstraintDropper(MySQLSchemaGenerator, ansisql.ANSIConstraintDropper):
-        def visit_migrate_check_constraint(self, *p, **k):
-            raise exceptions.NotSupportedError("MySQL does not support CHECK"
-                " constraints, use triggers instead.")
 
-else:
-    class MySQLConstraintDropper(ansisql.ANSIConstraintDropper):
-
-        def visit_migrate_primary_key_constraint(self, constraint):
-            self.start_alter_table(constraint)
-            self.append("DROP PRIMARY KEY")
-            self.execute()
-
-        def visit_migrate_foreign_key_constraint(self, constraint):
-            self.start_alter_table(constraint)
-            self.append("DROP FOREIGN KEY ")
-            constraint.name = self.get_constraint_name(constraint)
-            self.append(self.preparer.format_constraint(constraint))
-            self.execute()
-
-        def visit_migrate_check_constraint(self, *p, **k):
-            raise exceptions.NotSupportedError("MySQL does not support CHECK"
-                " constraints, use triggers instead.")
-
-        def visit_migrate_unique_constraint(self, constraint, *p, **k):
-            self.start_alter_table(constraint)
-            self.append('DROP INDEX ')
-            constraint.name = self.get_constraint_name(constraint)
-            self.append(self.preparer.format_constraint(constraint))
-            self.execute()
+class MySQLConstraintDropper(MySQLSchemaGenerator, ansisql.ANSIConstraintDropper):
+    def visit_migrate_check_constraint(self, *p, **k):
+        raise exceptions.NotSupportedError("MySQL does not support CHECK"
+            " constraints, use triggers instead.")
 
 
 class MySQLDialect(ansisql.ANSIDialect):
--- a/rhodecode/lib/dbmigrate/migrate/changeset/databases/postgres.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/postgres.py	Sun Feb 26 17:25:09 2012 +0200
@@ -3,14 +3,11 @@
 
    .. _`PostgreSQL`: http://www.postgresql.org/
 """
-from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
+from rhodecode.lib.dbmigrate.migrate.changeset import ansisql
+
 
-if not SQLA_06:
-    from sqlalchemy.databases import postgres as sa_base
-    PGSchemaGenerator = sa_base.PGSchemaGenerator
-else:
-    from sqlalchemy.databases import postgresql as sa_base
-    PGSchemaGenerator = sa_base.PGDDLCompiler
+from sqlalchemy.databases import postgresql as sa_base
+PGSchemaGenerator = sa_base.PGDDLCompiler
 
 
 class PGColumnGenerator(PGSchemaGenerator, ansisql.ANSIColumnGenerator):
--- a/rhodecode/lib/dbmigrate/migrate/changeset/databases/sqlite.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/sqlite.py	Sun Feb 26 17:25:09 2012 +0200
@@ -11,11 +11,8 @@
 from rhodecode.lib.dbmigrate.migrate import exceptions
 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
 
+SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler
 
-if not SQLA_06:
-    SQLiteSchemaGenerator = sa_base.SQLiteSchemaGenerator
-else:
-    SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler
 
 class SQLiteCommon(object):
 
@@ -39,7 +36,7 @@
 
         insertion_string = self._modify_table(table, column, delta)
 
-        table.create()
+        table.create(bind=self.connection)
         self.append(insertion_string % {'table_name': table_name})
         self.execute()
         self.append('DROP TABLE migration_tmp')
--- a/rhodecode/lib/dbmigrate/migrate/changeset/schema.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/changeset/schema.py	Sun Feb 26 17:25:09 2012 +0200
@@ -349,10 +349,7 @@
     def process_column(self, column):
         """Processes default values for column"""
         # XXX: this is a snippet from SA processing of positional parameters
-        if not SQLA_06 and column.args:
-            toinit = list(column.args)
-        else:
-            toinit = list()
+        toinit = list()
 
         if column.server_default is not None:
             if isinstance(column.server_default, sqlalchemy.FetchedValue):
@@ -368,9 +365,6 @@
         if toinit:
             column._init_items(*toinit)
 
-        if not SQLA_06:
-            column.args = []
-
     def _get_table(self):
         return getattr(self, '_table', None)
 
@@ -469,14 +463,18 @@
         self._set_parent(self.metadata)
 
     def _meta_key(self):
+        """Get the meta key for this table."""
         return sqlalchemy.schema._get_table_key(self.name, self.schema)
 
     def deregister(self):
         """Remove this table from its metadata"""
-        key = self._meta_key()
-        meta = self.metadata
-        if key in meta.tables:
-            del meta.tables[key]
+        if SQLA_07:
+            self.metadata._remove_table(self.name, self.schema)
+        else:
+            key = self._meta_key()
+            meta = self.metadata
+            if key in meta.tables:
+                del meta.tables[key]
 
 
 class ChangesetColumn(object):
--- a/rhodecode/lib/dbmigrate/migrate/exceptions.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/exceptions.py	Sun Feb 26 17:25:09 2012 +0200
@@ -83,6 +83,5 @@
 class InvalidConstraintError(Error):
     """Invalid constraint error"""
 
-
 class MigrateDeprecationWarning(DeprecationWarning):
     """Warning for deprecated features in Migrate"""
--- a/rhodecode/lib/dbmigrate/migrate/versioning/api.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/versioning/api.py	Sun Feb 26 17:25:09 2012 +0200
@@ -119,7 +119,7 @@
 
     For instance, manage.py script_sql postgresql description creates:
     repository/versions/001_description_postgresql_upgrade.sql and
-    repository/versions/001_description_postgresql_postgres.sql
+    repository/versions/001_description_postgresql_downgrade.sql
     """
     repo = Repository(repository)
     repo.create_script_sql(database, description, **opts)
@@ -212,14 +212,15 @@
     """
     engine = opts.pop('engine')
     repos = Repository(repository)
-    script = repos.version(None).script()
 
     # Upgrade
     log.info("Upgrading...")
+    script = repos.version(None).script(engine.name, 'upgrade')
     script.run(engine, 1)
     log.info("done")
 
     log.info("Downgrading...")
+    script = repos.version(None).script(engine.name, 'downgrade')
     script.run(engine, -1)
     log.info("done")
     log.info("Success")
--- a/rhodecode/lib/dbmigrate/migrate/versioning/genmodel.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/versioning/genmodel.py	Sun Feb 26 17:25:09 2012 +0200
@@ -282,4 +282,3 @@
                 except:
                     trans.rollback()
                     raise
-
--- a/rhodecode/lib/dbmigrate/migrate/versioning/repository.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/versioning/repository.py	Sun Feb 26 17:25:09 2012 +0200
@@ -115,7 +115,7 @@
         options.setdefault('version_table', 'migrate_version')
         options.setdefault('repository_id', name)
         options.setdefault('required_dbs', [])
-        options.setdefault('use_timestamp_numbering', '0')
+        options.setdefault('use_timestamp_numbering', False)
 
         tmpl = open(os.path.join(tmpl_dir, cls._config)).read()
         ret = TempitaTemplate(tmpl).substitute(options)
@@ -153,7 +153,7 @@
 
     def create_script(self, description, **k):
         """API to :meth:`migrate.versioning.version.Collection.create_new_python_version`"""
-        
+
         k['use_timestamp_numbering'] = self.use_timestamp_numbering
         self.versions.create_new_python_version(description, **k)
 
@@ -180,9 +180,9 @@
     @property
     def use_timestamp_numbering(self):
         """Returns use_timestamp_numbering specified in config"""
-        ts_numbering = self.config.get('db_settings', 'use_timestamp_numbering', raw=True)
-        
-        return ts_numbering
+        if self.config.has_option('db_settings', 'use_timestamp_numbering'):
+            return self.config.getboolean('db_settings', 'use_timestamp_numbering')
+        return False
 
     def version(self, *p, **k):
         """API to :attr:`migrate.versioning.version.Collection.version`"""
--- a/rhodecode/lib/dbmigrate/migrate/versioning/schemadiff.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/versioning/schemadiff.py	Sun Feb 26 17:25:09 2012 +0200
@@ -17,8 +17,16 @@
     :return: object which will evaluate to :keyword:`True` if there \
       are differences else :keyword:`False`.
     """
-    return SchemaDiff(metadata,
-                      sqlalchemy.MetaData(engine, reflect=True),
+    db_metadata = sqlalchemy.MetaData(engine, reflect=True)
+
+    # sqlite will include a dynamically generated 'sqlite_sequence' table if
+    # there are autoincrement sequences in the database; this should not be
+    # compared.
+    if engine.dialect.name == 'sqlite':
+        if 'sqlite_sequence' in db_metadata.tables:
+            db_metadata.remove(db_metadata.tables['sqlite_sequence'])
+
+    return SchemaDiff(metadata, db_metadata,
                       labelA='model',
                       labelB='database',
                       excludeTables=excludeTables)
@@ -31,7 +39,7 @@
     :return: object which will evaluate to :keyword:`True` if there \
       are differences else :keyword:`False`.
     """
-    return SchemaDiff(metadataA, metadataB, excludeTables)
+    return SchemaDiff(metadataA, metadataB, excludeTables=excludeTables)
 
 
 class ColDiff(object):
--- a/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage/default.py_tmpl	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage/default.py_tmpl	Sun Feb 26 17:25:09 2012 +0200
@@ -7,4 +7,6 @@
 _vars.pop('repository_name', None)
 defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()])
 }}
-main({{ defaults }})
+
+if __name__ == '__main__':
+    main({{ defaults }})
--- a/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage/pylons.py_tmpl	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage/pylons.py_tmpl	Sun Feb 26 17:25:09 2012 +0200
@@ -26,4 +26,5 @@
 
 # migrate supports passing url as an existing Engine instance (since 0.6.0)
 # usage: migrate -c path/to/config.ini COMMANDS
-main(url=engine_from_config(conf_dict), repository=migrations.__path__[0],{{ defaults }})
+if __name__ == '__main__':
+    main(url=engine_from_config(conf_dict), repository=migrations.__path__[0],{{ defaults }})
--- a/rhodecode/lib/dbmigrate/migrate/versioning/util/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/versioning/util/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -158,7 +158,7 @@
         kw['engine'] = engine
         return f(*a, **kw)
     finally:
-        if isinstance(engine, Engine):
+        if isinstance(engine, Engine) and engine is not url:
             log.debug('Disposing SQLAlchemy engine %s', engine)
             engine.dispose()
 
--- a/rhodecode/lib/dbmigrate/migrate/versioning/version.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/migrate/versioning/version.py	Sun Feb 26 17:25:09 2012 +0200
@@ -60,7 +60,7 @@
         and store them in self.versions
         """
         super(Collection, self).__init__(path)
-        
+
         # Create temporary list of files, allowing skipped version numbers.
         files = os.listdir(path)
         if '1' in files:
@@ -90,9 +90,7 @@
         return max([VerNum(0)] + self.versions.keys())
 
     def _next_ver_num(self, use_timestamp_numbering):
-        print use_timestamp_numbering
         if use_timestamp_numbering == True:
-            print "Creating new timestamp version!"
             return VerNum(int(datetime.utcnow().strftime('%Y%m%d%H%M%S')))
         else:
             return self.latest + 1
@@ -113,7 +111,7 @@
 
         script.PythonScript.create(filepath, **k)
         self.versions[ver] = Version(ver, self.path, [filename])
-        
+
     def create_new_sql_version(self, database, description, **k):
         """Create SQL files for new version"""
         ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
@@ -133,7 +131,7 @@
             filepath = self._version_path(filename)
             script.SqlScript.create(filepath, **k)
             self.versions[ver].add_script(filepath)
-        
+
     def version(self, vernum=None):
         """Returns latest Version if vernum is not given.
         Otherwise, returns wanted version"""
@@ -152,7 +150,7 @@
 
 class Version(object):
     """A single version in a collection
-    :param vernum: Version Number 
+    :param vernum: Version Number
     :param path: Path to script files
     :param filelist: List of scripts
     :type vernum: int, VerNum
@@ -169,7 +167,7 @@
 
         for script in filelist:
             self.add_script(os.path.join(path, script))
-    
+
     def script(self, database=None, operation=None):
         """Returns SQL or Python Script"""
         for db in (database, 'default'):
@@ -198,7 +196,7 @@
     def _add_script_sql(self, path):
         basename = os.path.basename(path)
         match = self.SQL_FILENAME.match(basename)
-        
+
         if match:
             basename = basename.replace('.sql', '')
             parts = basename.split('_')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/schema/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.lib.dbmigrate.schema
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+    Schemas for migrations
+
+
+    :created_on: Nov 1, 2011
+    :author: marcink
+    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
+    :license: <name>, see LICENSE_FILE for more details.
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/schema/db_1_1_0.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,94 @@
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper
+from sqlalchemy.orm.session import Session
+from rhodecode.model.meta import Base
+
+class BaseModel(object):
+    """Base Model for all classess
+
+    """
+
+    @classmethod
+    def _get_keys(cls):
+        """return column names for this model """
+        return class_mapper(cls).c.keys()
+
+    def get_dict(self):
+        """return dict with keys and values corresponding
+        to this model data """
+
+        d = {}
+        for k in self._get_keys():
+            d[k] = getattr(self, k)
+        return d
+
+    def get_appstruct(self):
+        """return list with keys and values tupples corresponding
+        to this model data """
+
+        l = []
+        for k in self._get_keys():
+            l.append((k, getattr(self, k),))
+        return l
+
+    def populate_obj(self, populate_dict):
+        """populate model with data from given populate_dict"""
+
+        for k in self._get_keys():
+            if k in populate_dict:
+                setattr(self, k, populate_dict[k])
+
+    @classmethod
+    def query(cls):
+        return Session.query(cls)
+
+    @classmethod
+    def get(cls, id_):
+        if id_:
+            return cls.query().get(id_)
+
+    @classmethod
+    def getAll(cls):
+        return cls.query().all()
+
+    @classmethod
+    def delete(cls, id_):
+        obj = cls.query().get(id_)
+        Session.delete(obj)
+        Session.commit()
+
+
+class UserFollowing(Base, BaseModel):
+    __tablename__ = 'user_followings'
+    __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
+                      UniqueConstraint('user_id', 'follows_user_id')
+                      , {'useexisting':True})
+
+    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
+    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
+    follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
+
+    user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
+
+    follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
+    follows_repository = relation('Repository')
+
+
+class CacheInvalidation(Base, BaseModel):
+    __tablename__ = 'cache_invalidation'
+    __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
+    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+
+
+    def __init__(self, cache_key, cache_args=''):
+        self.cache_key = cache_key
+        self.cache_args = cache_args
+        self.cache_active = False
+
+    def __repr__(self):
+        return "<CacheInvalidation('%s:%s')>" % (self.cache_id, self.cache_key)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,1098 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.model.db
+    ~~~~~~~~~~~~~~~~~~
+
+    Database Models for RhodeCode
+
+    :created_on: Apr 08, 2010
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+import datetime
+import traceback
+from datetime import date
+
+from sqlalchemy import *
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
+from beaker.cache import cache_region, region_invalidate
+
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+
+from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
+    generate_api_key, safe_unicode
+from rhodecode.lib.exceptions import UsersGroupsAssignedException
+from rhodecode.lib.compat import json
+
+from rhodecode.model.meta import Base, Session
+from rhodecode.lib.caching_query import FromCache
+
+
+log = logging.getLogger(__name__)
+
+#==============================================================================
+# BASE CLASSES
+#==============================================================================
+
+class ModelSerializer(json.JSONEncoder):
+    """
+    Simple Serializer for JSON,
+
+    usage::
+
+        to make object customized for serialization implement a __json__
+        method that will return a dict for serialization into json
+
+    example::
+
+        class Task(object):
+
+            def __init__(self, name, value):
+                self.name = name
+                self.value = value
+
+            def __json__(self):
+                return dict(name=self.name,
+                            value=self.value)
+
+    """
+
+    def default(self, obj):
+
+        if hasattr(obj, '__json__'):
+            return obj.__json__()
+        else:
+            return json.JSONEncoder.default(self, obj)
+
+class BaseModel(object):
+    """Base Model for all classess
+
+    """
+
+    @classmethod
+    def _get_keys(cls):
+        """return column names for this model """
+        return class_mapper(cls).c.keys()
+
+    def get_dict(self):
+        """return dict with keys and values corresponding
+        to this model data """
+
+        d = {}
+        for k in self._get_keys():
+            d[k] = getattr(self, k)
+        return d
+
+    def get_appstruct(self):
+        """return list with keys and values tupples corresponding
+        to this model data """
+
+        l = []
+        for k in self._get_keys():
+            l.append((k, getattr(self, k),))
+        return l
+
+    def populate_obj(self, populate_dict):
+        """populate model with data from given populate_dict"""
+
+        for k in self._get_keys():
+            if k in populate_dict:
+                setattr(self, k, populate_dict[k])
+
+    @classmethod
+    def query(cls):
+        return Session.query(cls)
+
+    @classmethod
+    def get(cls, id_):
+        if id_:
+            return cls.query().get(id_)
+
+    @classmethod
+    def getAll(cls):
+        return cls.query().all()
+
+    @classmethod
+    def delete(cls, id_):
+        obj = cls.query().get(id_)
+        Session.delete(obj)
+        Session.commit()
+
+
+class RhodeCodeSetting(Base, BaseModel):
+    __tablename__ = 'rhodecode_settings'
+    __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
+    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __init__(self, k='', v=''):
+        self.app_settings_name = k
+        self.app_settings_value = v
+
+
+    @validates('_app_settings_value')
+    def validate_settings_value(self, key, val):
+        assert type(val) == unicode
+        return val
+
+    @hybrid_property
+    def app_settings_value(self):
+        v = self._app_settings_value
+        if v == 'ldap_active':
+            v = str2bool(v)
+        return v
+
+    @app_settings_value.setter
+    def app_settings_value(self, val):
+        """
+        Setter that will always make sure we use unicode in app_settings_value
+
+        :param val:
+        """
+        self._app_settings_value = safe_unicode(val)
+
+    def __repr__(self):
+        return "<%s('%s:%s')>" % (self.__class__.__name__,
+                                  self.app_settings_name, self.app_settings_value)
+
+
+    @classmethod
+    def get_by_name(cls, ldap_key):
+        return cls.query()\
+            .filter(cls.app_settings_name == ldap_key).scalar()
+
+    @classmethod
+    def get_app_settings(cls, cache=False):
+
+        ret = cls.query()
+
+        if cache:
+            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
+
+        if not ret:
+            raise Exception('Could not get application settings !')
+        settings = {}
+        for each in ret:
+            settings['rhodecode_' + each.app_settings_name] = \
+                each.app_settings_value
+
+        return settings
+
+    @classmethod
+    def get_ldap_settings(cls, cache=False):
+        ret = cls.query()\
+                .filter(cls.app_settings_name.startswith('ldap_')).all()
+        fd = {}
+        for row in ret:
+            fd.update({row.app_settings_name:row.app_settings_value})
+
+        return fd
+
+
+class RhodeCodeUi(Base, BaseModel):
+    __tablename__ = 'rhodecode_ui'
+    __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
+
+    HOOK_UPDATE = 'changegroup.update'
+    HOOK_REPO_SIZE = 'changegroup.repo_size'
+    HOOK_PUSH = 'pretxnchangegroup.push_logger'
+    HOOK_PULL = 'preoutgoing.pull_logger'
+
+    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
+
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.ui_key == key)
+
+
+    @classmethod
+    def get_builtin_hooks(cls):
+        q = cls.query()
+        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
+                                    cls.HOOK_REPO_SIZE,
+                                    cls.HOOK_PUSH, cls.HOOK_PULL]))
+        return q.all()
+
+    @classmethod
+    def get_custom_hooks(cls):
+        q = cls.query()
+        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
+                                    cls.HOOK_REPO_SIZE,
+                                    cls.HOOK_PUSH, cls.HOOK_PULL]))
+        q = q.filter(cls.ui_section == 'hooks')
+        return q.all()
+
+    @classmethod
+    def create_or_update_hook(cls, key, val):
+        new_ui = cls.get_by_key(key).scalar() or cls()
+        new_ui.ui_section = 'hooks'
+        new_ui.ui_active = True
+        new_ui.ui_key = key
+        new_ui.ui_value = val
+
+        Session.add(new_ui)
+        Session.commit()
+
+
+class User(Base, BaseModel):
+    __tablename__ = 'users'
+    __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
+    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)
+    password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=None)
+    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+    name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
+    ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    user_log = relationship('UserLog', cascade='all')
+    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
+
+    repositories = relationship('Repository')
+    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
+    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
+
+    group_member = relationship('UsersGroupMember', cascade='all')
+
+    @property
+    def full_contact(self):
+        return '%s %s <%s>' % (self.name, self.lastname, self.email)
+
+    @property
+    def short_contact(self):
+        return '%s %s' % (self.name, self.lastname)
+
+    @property
+    def is_admin(self):
+        return self.admin
+
+    def __repr__(self):
+        try:
+            return "<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                             self.user_id, self.username)
+        except:
+            return self.__class__.__name__
+
+    @classmethod
+    def get_by_username(cls, username, case_insensitive=False):
+        if case_insensitive:
+            return Session.query(cls).filter(cls.username.ilike(username)).scalar()
+        else:
+            return Session.query(cls).filter(cls.username == username).scalar()
+
+    @classmethod
+    def get_by_api_key(cls, api_key):
+        return cls.query().filter(cls.api_key == api_key).one()
+
+    def update_lastlogin(self):
+        """Update user lastlogin"""
+
+        self.last_login = datetime.datetime.now()
+        Session.add(self)
+        Session.commit()
+        log.debug('updated user %s lastlogin' % self.username)
+
+    @classmethod
+    def create(cls, form_data):
+        from rhodecode.lib.auth import get_crypt_password
+
+        try:
+            new_user = cls()
+            for k, v in form_data.items():
+                if k == 'password':
+                    v = get_crypt_password(v)
+                setattr(new_user, k, v)
+
+            new_user.api_key = generate_api_key(form_data['username'])
+            Session.add(new_user)
+            Session.commit()
+            return new_user
+        except:
+            log.error(traceback.format_exc())
+            Session.rollback()
+            raise
+
+class UserLog(Base, BaseModel):
+    __tablename__ = 'user_logs'
+    __table_args__ = {'extend_existing':True}
+    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
+
+    @property
+    def action_as_day(self):
+        return date(*self.action_date.timetuple()[:3])
+
+    user = relationship('User')
+    repository = relationship('Repository')
+
+
+class UsersGroup(Base, BaseModel):
+    __tablename__ = 'users_groups'
+    __table_args__ = {'extend_existing':True}
+
+    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
+
+    members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
+
+    def __repr__(self):
+        return '<userGroup(%s)>' % (self.users_group_name)
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+        if case_insensitive:
+            gr = cls.query()\
+                .filter(cls.users_group_name.ilike(group_name))
+        else:
+            gr = cls.query()\
+                .filter(cls.users_group_name == group_name)
+        if cache:
+            gr = gr.options(FromCache("sql_cache_short",
+                                          "get_user_%s" % group_name))
+        return gr.scalar()
+
+
+    @classmethod
+    def get(cls, users_group_id, cache=False):
+        users_group = cls.query()
+        if cache:
+            users_group = users_group.options(FromCache("sql_cache_short",
+                                    "get_users_group_%s" % users_group_id))
+        return users_group.get(users_group_id)
+
+    @classmethod
+    def create(cls, form_data):
+        try:
+            new_users_group = cls()
+            for k, v in form_data.items():
+                setattr(new_users_group, k, v)
+
+            Session.add(new_users_group)
+            Session.commit()
+            return new_users_group
+        except:
+            log.error(traceback.format_exc())
+            Session.rollback()
+            raise
+
+    @classmethod
+    def update(cls, users_group_id, form_data):
+
+        try:
+            users_group = cls.get(users_group_id, cache=False)
+
+            for k, v in form_data.items():
+                if k == 'users_group_members':
+                    users_group.members = []
+                    Session.flush()
+                    members_list = []
+                    if v:
+                        v = [v] if isinstance(v, basestring) else v
+                        for u_id in set(v):
+                            member = UsersGroupMember(users_group_id, u_id)
+                            members_list.append(member)
+                    setattr(users_group, 'members', members_list)
+                setattr(users_group, k, v)
+
+            Session.add(users_group)
+            Session.commit()
+        except:
+            log.error(traceback.format_exc())
+            Session.rollback()
+            raise
+
+    @classmethod
+    def delete(cls, users_group_id):
+        try:
+
+            # check if this group is not assigned to repo
+            assigned_groups = UsersGroupRepoToPerm.query()\
+                .filter(UsersGroupRepoToPerm.users_group_id ==
+                        users_group_id).all()
+
+            if assigned_groups:
+                raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
+                                                   assigned_groups)
+
+            users_group = cls.get(users_group_id, cache=False)
+            Session.delete(users_group)
+            Session.commit()
+        except:
+            log.error(traceback.format_exc())
+            Session.rollback()
+            raise
+
+class UsersGroupMember(Base, BaseModel):
+    __tablename__ = 'users_groups_members'
+    __table_args__ = {'extend_existing':True}
+
+    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User', lazy='joined')
+    users_group = relationship('UsersGroup')
+
+    def __init__(self, gr_id='', u_id=''):
+        self.users_group_id = gr_id
+        self.user_id = u_id
+
+    @staticmethod
+    def add_user_to_group(group, user):
+        ugm = UsersGroupMember()
+        ugm.users_group = group
+        ugm.user = user
+        Session.add(ugm)
+        Session.commit()
+        return ugm
+
+class Repository(Base, BaseModel):
+    __tablename__ = 'repositories'
+    __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
+
+    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
+    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
+    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
+    description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+
+    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
+
+
+    user = relationship('User')
+    fork = relationship('Repository', remote_side=repo_id)
+    group = relationship('RepoGroup')
+    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
+    users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
+    stats = relationship('Statistics', cascade='all', uselist=False)
+
+    followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
+
+    logs = relationship('UserLog', cascade='all')
+
+    def __repr__(self):
+        return "<%s('%s:%s')>" % (self.__class__.__name__,
+                                  self.repo_id, self.repo_name)
+
+    @classmethod
+    def url_sep(cls):
+        return '/'
+
+    @classmethod
+    def get_by_repo_name(cls, repo_name):
+        q = Session.query(cls).filter(cls.repo_name == repo_name)
+        q = q.options(joinedload(Repository.fork))\
+                .options(joinedload(Repository.user))\
+                .options(joinedload(Repository.group))
+        return q.one()
+
+    @classmethod
+    def get_repo_forks(cls, repo_id):
+        return cls.query().filter(Repository.fork_id == repo_id)
+
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all repos are stored
+
+        :param cls:
+        """
+        q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+                                              cls.url_sep())
+        q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def just_name(self):
+        return self.repo_name.split(Repository.url_sep())[-1]
+
+    @property
+    def groups_with_parents(self):
+        groups = []
+        if self.group is None:
+            return groups
+
+        cur_gr = self.group
+        groups.insert(0, cur_gr)
+        while 1:
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            groups.insert(0, gr)
+
+        return groups
+
+    @property
+    def groups_and_repo(self):
+        return self.groups_with_parents, self.just_name
+
+    @LazyProperty
+    def repo_path(self):
+        """
+        Returns base full path for that repository means where it actually
+        exists on a filesystem
+        """
+        q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+                                              Repository.url_sep())
+        q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
+    @property
+    def repo_full_path(self):
+        p = [self.repo_path]
+        # we need to split the name by / since this is how we store the
+        # names in the database, but that eventually needs to be converted
+        # into a valid system path
+        p += self.repo_name.split(Repository.url_sep())
+        return os.path.join(*p)
+
+    def get_new_name(self, repo_name):
+        """
+        returns new full repository name based on assigned group and new new
+
+        :param group_name:
+        """
+        path_prefix = self.group.full_path_splitted if self.group else []
+        return Repository.url_sep().join(path_prefix + [repo_name])
+
+    @property
+    def _ui(self):
+        """
+        Creates an db based ui object for this repository
+        """
+        from mercurial import ui
+        from mercurial import config
+        baseui = ui.ui()
+
+        #clean the baseui object
+        baseui._ocfg = config.config()
+        baseui._ucfg = config.config()
+        baseui._tcfg = config.config()
+
+
+        ret = RhodeCodeUi.query()\
+            .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
+
+        hg_ui = ret
+        for ui_ in hg_ui:
+            if ui_.ui_active:
+                log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
+                          ui_.ui_key, ui_.ui_value)
+                baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
+
+        return baseui
+
+    @classmethod
+    def is_valid(cls, repo_name):
+        """
+        returns True if given repo name is a valid filesystem repository
+
+        :param cls:
+        :param repo_name:
+        """
+        from rhodecode.lib.utils import is_valid_repo
+
+        return is_valid_repo(repo_name, cls.base_path())
+
+
+    #==========================================================================
+    # SCM PROPERTIES
+    #==========================================================================
+
+    def get_changeset(self, rev):
+        return get_changeset_safe(self.scm_instance, rev)
+
+    @property
+    def tip(self):
+        return self.get_changeset('tip')
+
+    @property
+    def author(self):
+        return self.tip.author
+
+    @property
+    def last_change(self):
+        return self.scm_instance.last_change
+
+    #==========================================================================
+    # SCM CACHE INSTANCE
+    #==========================================================================
+
+    @property
+    def invalidate(self):
+        return CacheInvalidation.invalidate(self.repo_name)
+
+    def set_invalidate(self):
+        """
+        set a cache for invalidation for this instance
+        """
+        CacheInvalidation.set_invalidate(self.repo_name)
+
+    @LazyProperty
+    def scm_instance(self):
+        return self.__get_instance()
+
+    @property
+    def scm_instance_cached(self):
+        @cache_region('long_term')
+        def _c(repo_name):
+            return self.__get_instance()
+        rn = self.repo_name
+
+        inv = self.invalidate
+        if inv is not None:
+            region_invalidate(_c, None, rn)
+            # update our cache
+            CacheInvalidation.set_valid(inv.cache_key)
+        return _c(rn)
+
+    def __get_instance(self):
+
+        repo_full_path = self.repo_full_path
+
+        try:
+            alias = get_scm(repo_full_path)[0]
+            log.debug('Creating instance of %s repository' % alias)
+            backend = get_backend(alias)
+        except VCSError:
+            log.error(traceback.format_exc())
+            log.error('Perhaps this repository is in db and not in '
+                      'filesystem run rescan repositories with '
+                      '"destroy old data " option from admin panel')
+            return
+
+        if alias == 'hg':
+
+            repo = backend(safe_str(repo_full_path), create=False,
+                           baseui=self._ui)
+            # skip hidden web repository
+            if repo._get_hidden():
+                return
+        else:
+            repo = backend(repo_full_path, create=False)
+
+        return repo
+
+
+class RepoGroup(Base, BaseModel):
+    __tablename__ = 'groups'
+    __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
+                      CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
+    __mapper_args__ = {'order_by':'group_name'}
+
+    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
+    group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    parent_group = relationship('RepoGroup', remote_side=group_id)
+
+
+    def __init__(self, group_name='', parent_group=None):
+        self.group_name = group_name
+        self.parent_group = parent_group
+
+    def __repr__(self):
+        return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
+                                  self.group_name)
+
+    @classmethod
+    def groups_choices(cls):
+        from webhelpers.html import literal as _literal
+        repo_groups = [('', '')]
+        sep = ' &raquo; '
+        _name = lambda k: _literal(sep.join(k))
+
+        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
+                              for x in cls.query().all()])
+
+        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
+        return repo_groups
+
+    @classmethod
+    def url_sep(cls):
+        return '/'
+
+    @classmethod
+    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+        if case_insensitive:
+            gr = cls.query()\
+                .filter(cls.group_name.ilike(group_name))
+        else:
+            gr = cls.query()\
+                .filter(cls.group_name == group_name)
+        if cache:
+            gr = gr.options(FromCache("sql_cache_short",
+                                          "get_group_%s" % group_name))
+        return gr.scalar()
+
+    @property
+    def parents(self):
+        parents_recursion_limit = 5
+        groups = []
+        if self.parent_group is None:
+            return groups
+        cur_gr = self.parent_group
+        groups.insert(0, cur_gr)
+        cnt = 0
+        while 1:
+            cnt += 1
+            gr = getattr(cur_gr, 'parent_group', None)
+            cur_gr = cur_gr.parent_group
+            if gr is None:
+                break
+            if cnt == parents_recursion_limit:
+                # this will prevent accidental infinit loops
+                log.error('group nested more than %s' %
+                          parents_recursion_limit)
+                break
+
+            groups.insert(0, gr)
+        return groups
+
+    @property
+    def children(self):
+        return Group.query().filter(Group.parent_group == self)
+
+    @property
+    def name(self):
+        return self.group_name.split(Group.url_sep())[-1]
+
+    @property
+    def full_path(self):
+        return self.group_name
+
+    @property
+    def full_path_splitted(self):
+        return self.group_name.split(Group.url_sep())
+
+    @property
+    def repositories(self):
+        return Repository.query().filter(Repository.group == self)
+
+    @property
+    def repositories_recursive_count(self):
+        cnt = self.repositories.count()
+
+        def children_count(group):
+            cnt = 0
+            for child in group.children:
+                cnt += child.repositories.count()
+                cnt += children_count(child)
+            return cnt
+
+        return cnt + children_count(self)
+
+
+    def get_new_name(self, group_name):
+        """
+        returns new full group name based on parent and new name
+
+        :param group_name:
+        """
+        path_prefix = (self.parent_group.full_path_splitted if
+                       self.parent_group else [])
+        return Group.url_sep().join(path_prefix + [group_name])
+
+
+class Permission(Base, BaseModel):
+    __tablename__ = 'permissions'
+    __table_args__ = {'extend_existing':True}
+    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')>" % (self.__class__.__name__,
+                                  self.permission_id, self.permission_name)
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.permission_name == key).scalar()
+
+class UserRepoToPerm(Base, BaseModel):
+    __tablename__ = 'repo_to_perm'
+    __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
+    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    permission = relationship('Permission')
+    repository = relationship('Repository')
+
+class UserToPerm(Base, BaseModel):
+    __tablename__ = 'user_to_perm'
+    __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
+    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    permission = relationship('Permission')
+
+    @classmethod
+    def has_perm(cls, user_id, perm):
+        if not isinstance(perm, Permission):
+            raise Exception('perm needs to be an instance of Permission class')
+
+        return cls.query().filter(cls.user_id == user_id)\
+            .filter(cls.permission == perm).scalar() is not None
+
+    @classmethod
+    def grant_perm(cls, user_id, perm):
+        if not isinstance(perm, Permission):
+            raise Exception('perm needs to be an instance of Permission class')
+
+        new = cls()
+        new.user_id = user_id
+        new.permission = perm
+        try:
+            Session.add(new)
+            Session.commit()
+        except:
+            Session.rollback()
+
+
+    @classmethod
+    def revoke_perm(cls, user_id, perm):
+        if not isinstance(perm, Permission):
+            raise Exception('perm needs to be an instance of Permission class')
+
+        try:
+            cls.query().filter(cls.user_id == user_id)\
+                .filter(cls.permission == perm).delete()
+            Session.commit()
+        except:
+            Session.rollback()
+
+class UsersGroupRepoToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_to_perm'
+    __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UsersGroup')
+    permission = relationship('Permission')
+    repository = relationship('Repository')
+
+    def __repr__(self):
+        return '<userGroup:%s => %s >' % (self.users_group, self.repository)
+
+class UsersGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_to_perm'
+    __table_args__ = {'extend_existing':True}
+    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UsersGroup')
+    permission = relationship('Permission')
+
+
+    @classmethod
+    def has_perm(cls, users_group_id, perm):
+        if not isinstance(perm, Permission):
+            raise Exception('perm needs to be an instance of Permission class')
+
+        return cls.query().filter(cls.users_group_id ==
+                                         users_group_id)\
+                                         .filter(cls.permission == perm)\
+                                         .scalar() is not None
+
+    @classmethod
+    def grant_perm(cls, users_group_id, perm):
+        if not isinstance(perm, Permission):
+            raise Exception('perm needs to be an instance of Permission class')
+
+        new = cls()
+        new.users_group_id = users_group_id
+        new.permission = perm
+        try:
+            Session.add(new)
+            Session.commit()
+        except:
+            Session.rollback()
+
+
+    @classmethod
+    def revoke_perm(cls, users_group_id, perm):
+        if not isinstance(perm, Permission):
+            raise Exception('perm needs to be an instance of Permission class')
+
+        try:
+            cls.query().filter(cls.users_group_id == users_group_id)\
+                .filter(cls.permission == perm).delete()
+            Session.commit()
+        except:
+            Session.rollback()
+
+
+class UserRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'group_to_perm'
+    __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
+
+    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+
+    user = relationship('User')
+    permission = relationship('Permission')
+    group = relationship('RepoGroup')
+
+class Statistics(Base, BaseModel):
+    __tablename__ = 'statistics'
+    __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
+    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
+    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
+    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
+    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
+
+    repository = relationship('Repository', single_parent=True)
+
+class UserFollowing(Base, BaseModel):
+    __tablename__ = 'user_followings'
+    __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
+                      UniqueConstraint('user_id', 'follows_user_id')
+                      , {'extend_existing':True})
+
+    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
+    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+
+    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
+
+    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
+    follows_repository = relationship('Repository', order_by='Repository.repo_name')
+
+
+    @classmethod
+    def get_repo_followers(cls, repo_id):
+        return cls.query().filter(cls.follows_repo_id == repo_id)
+
+class CacheInvalidation(Base, BaseModel):
+    __tablename__ = 'cache_invalidation'
+    __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
+    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)
+    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+
+
+    def __init__(self, cache_key, cache_args=''):
+        self.cache_key = cache_key
+        self.cache_args = cache_args
+        self.cache_active = False
+
+    def __repr__(self):
+        return "<%s('%s:%s')>" % (self.__class__.__name__,
+                                  self.cache_id, self.cache_key)
+
+    @classmethod
+    def invalidate(cls, key):
+        """
+        Returns Invalidation object if this given key should be invalidated
+        None otherwise. `cache_active = False` means that this cache
+        state is not valid and needs to be invalidated
+
+        :param key:
+        """
+        return cls.query()\
+                .filter(CacheInvalidation.cache_key == key)\
+                .filter(CacheInvalidation.cache_active == False)\
+                .scalar()
+
+    @classmethod
+    def set_invalidate(cls, key):
+        """
+        Mark this Cache key for invalidation
+
+        :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)
+
+        try:
+            Session.add(inv_obj)
+            Session.commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            Session.rollback()
+
+    @classmethod
+    def set_valid(cls, key):
+        """
+        Mark this cache key as active and currently cached
+
+        :param key:
+        """
+        inv_obj = Session.query(CacheInvalidation)\
+            .filter(CacheInvalidation.cache_key == key).scalar()
+        inv_obj.cache_active = True
+        Session.add(inv_obj)
+        Session.commit()
+
+class DbMigrateVersion(Base, BaseModel):
+    __tablename__ = 'db_migrate_version'
+    __table_args__ = {'extend_existing':True}
+    repository_id = Column('repository_id', String(250), primary_key=True)
+    repository_path = Column('repository_path', Text)
+    version = Column('version', Integer)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.model.db
+    ~~~~~~~~~~~~~~~~~~
+
+    Database Models for RhodeCode
+
+    :created_on: Apr 08, 2010
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (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/>.
+
+#TODO: when branch 1.3 is finished replacem with db.py content
+
+from rhodecode.model.db import *
--- a/rhodecode/lib/dbmigrate/versions/001_initial_release.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/versions/001_initial_release.py	Sun Feb 26 17:25:09 2012 +0200
@@ -14,7 +14,7 @@
 
 log = logging.getLogger(__name__)
 
-class RhodeCodeSettings(Base):
+class RhodeCodeSetting(Base):
     __tablename__ = 'rhodecode_settings'
     __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
     app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -74,7 +74,7 @@
             self.last_login = datetime.datetime.now()
             session.add(self)
             session.commit()
-            log.debug('updated user %s lastlogin', self.username)
+            log.debug('updated user %s lastlogin' % self.username)
         except (DatabaseError,):
             session.rollback()
 
@@ -107,7 +107,7 @@
 
     user = relation('User')
     fork = relation('Repository', remote_side=repo_id)
-    repo_to_perm = relation('RepoToPerm', cascade='all')
+    repo_to_perm = relation('UserRepoToPerm', cascade='all')
     stats = relation('Statistics', cascade='all', uselist=False)
 
     repo_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
@@ -126,7 +126,7 @@
     def __repr__(self):
         return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
 
-class RepoToPerm(Base):
+class UserRepoToPerm(Base):
     __tablename__ = 'repo_to_perm'
     __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
     repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
--- a/rhodecode/lib/dbmigrate/versions/002_version_1_1_0.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/versions/002_version_1_1_0.py	Sun Feb 26 17:25:09 2012 +0200
@@ -12,6 +12,7 @@
 
 log = logging.getLogger(__name__)
 
+
 def upgrade(migrate_engine):
     """ Upgrade operations go here.
     Don't create your own engine; bind migrate_engine to your metadata
@@ -44,8 +45,6 @@
                       nullable=True, unique=None, default=None)
     revision.create(tbl)
 
-
-
     #==========================================================================
     # Upgrade of `repositories` table
     #==========================================================================
@@ -69,47 +68,18 @@
     #==========================================================================
     # Add table `user_followings`
     #==========================================================================
-    class UserFollowing(Base, BaseModel):
-        __tablename__ = 'user_followings'
-        __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
-                          UniqueConstraint('user_id', 'follows_user_id')
-                          , {'useexisting':True})
-
-        user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-        user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
-        follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
-        follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
-
-        user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-        follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-        follows_repository = relation('Repository')
-
+    from rhodecode.lib.dbmigrate.schema.db_1_1_0 import UserFollowing
     UserFollowing().__table__.create()
 
     #==========================================================================
     # Add table `cache_invalidation`
     #==========================================================================
-    class CacheInvalidation(Base, BaseModel):
-        __tablename__ = 'cache_invalidation'
-        __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
-        cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-        cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-        cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-        cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
-
-
-        def __init__(self, cache_key, cache_args=''):
-            self.cache_key = cache_key
-            self.cache_args = cache_args
-            self.cache_active = False
-
-        def __repr__(self):
-            return "<CacheInvalidation('%s:%s')>" % (self.cache_id, self.cache_key)
+    from rhodecode.lib.dbmigrate.schema.db_1_1_0 import CacheInvalidation
     CacheInvalidation().__table__.create()
 
     return
 
+
 def downgrade(migrate_engine):
     meta = MetaData()
     meta.bind = migrate_engine
--- a/rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py	Sun Feb 26 17:25:09 2012 +0200
@@ -13,6 +13,7 @@
 
 log = logging.getLogger(__name__)
 
+
 def upgrade(migrate_engine):
     """ Upgrade operations go here.
     Don't create your own engine; bind migrate_engine to your metadata
@@ -21,46 +22,46 @@
     #==========================================================================
     # Add table `groups``
     #==========================================================================
-    from rhodecode.model.db import Group
+    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import RepoGroup as Group
     Group().__table__.create()
 
     #==========================================================================
     # Add table `group_to_perm`
     #==========================================================================
-    from rhodecode.model.db import GroupToPerm
-    GroupToPerm().__table__.create()
+    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UserRepoGroupToPerm
+    UserRepoGroupToPerm().__table__.create()
 
     #==========================================================================
     # Add table `users_groups`
     #==========================================================================
-    from rhodecode.model.db import UsersGroup
+    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroup
     UsersGroup().__table__.create()
 
     #==========================================================================
     # Add table `users_groups_members`
     #==========================================================================
-    from rhodecode.model.db import UsersGroupMember
+    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupMember
     UsersGroupMember().__table__.create()
 
     #==========================================================================
     # Add table `users_group_repo_to_perm`
     #==========================================================================
-    from rhodecode.model.db import UsersGroupRepoToPerm
+    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupRepoToPerm
     UsersGroupRepoToPerm().__table__.create()
 
     #==========================================================================
     # Add table `users_group_to_perm`
     #==========================================================================
-    from rhodecode.model.db import UsersGroupToPerm
+    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupToPerm
     UsersGroupToPerm().__table__.create()
 
     #==========================================================================
     # Upgrade of `users` table
     #==========================================================================
-    from rhodecode.model.db import User
+    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import User
 
     #add column
-    ldap_dn = Column("ldap_dn", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
     ldap_dn.create(User().__table__)
 
     api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -74,7 +75,7 @@
     #==========================================================================
     # Upgrade of `repositories` table
     #==========================================================================
-    from rhodecode.model.db import Repository
+    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import Repository
 
     #ADD clone_uri column#
 
@@ -83,7 +84,7 @@
                         nullable=True, unique=False, default=None)
 
     clone_uri.create(Repository().__table__)
-    
+
     #ADD downloads column#
     enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
     enable_downloads.create(Repository().__table__)
@@ -104,10 +105,10 @@
     # Upgrade of `user_followings` table
     #==========================================================================
 
-    from rhodecode.model.db import UserFollowing
+    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UserFollowing
 
-    follows_from = Column('follows_from', DateTime(timezone=False), 
-                          nullable=True, unique=None, 
+    follows_from = Column('follows_from', DateTime(timezone=False),
+                          nullable=True, unique=None,
                           default=datetime.datetime.now)
     follows_from.create(UserFollowing().__table__)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/004_version_1_3_0.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,74 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper
+from sqlalchemy.orm.session import Session
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+
+from rhodecode.model.meta import Base
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """ Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+    #==========================================================================
+    # Add table `users_group_repo_group_to_perm`
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UsersGroupRepoGroupToPerm
+    UsersGroupRepoGroupToPerm().__table__.create()
+
+    #==========================================================================
+    # Add table `changeset_comments`
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import  ChangesetComment
+    ChangesetComment().__table__.create()
+
+    #==========================================================================
+    # Add table `notifications`
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import  Notification
+    Notification().__table__.create()
+
+    #==========================================================================
+    # Add table `user_to_notification`
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import  UserNotification
+    UserNotification().__table__.create()
+
+    #==========================================================================
+    # Add unique to table `users_group_to_perm`
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UsersGroupToPerm
+    tbl = UsersGroupToPerm().__table__
+    cons = UniqueConstraint('users_group_id', 'permission_id', table=tbl)
+    cons.create()
+
+    #==========================================================================
+    # Fix unique constrain on table `user_logs`
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UserLog
+    tbl = UserLog().__table__
+    col = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'),
+                 nullable=False, unique=None, default=None)
+    col.alter(nullable=True, table=tbl)
+
+    #==========================================================================
+    # Rename table `group_to_perm` to `user_repo_group_to_perm`
+    #==========================================================================
+    tbl = Table('group_to_perm', MetaData(bind=migrate_engine), autoload=True,
+                    autoload_with=migrate_engine)
+    tbl.rename('user_repo_group_to_perm')
+
+    return
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/dbmigrate/versions/005_version_1_3_0.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,65 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper
+from sqlalchemy.orm.session import Session
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+
+from rhodecode.model.meta import Base
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """ Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+
+    #==========================================================================
+    # Change unique constraints of table `repo_to_perm`
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UserRepoToPerm
+    tbl = UserRepoToPerm().__table__
+    new_cons = UniqueConstraint('user_id', 'repository_id', 'permission_id', table=tbl)
+    new_cons.create()
+
+    if migrate_engine.name in ['mysql']:
+        old_cons = UniqueConstraint('user_id', 'repository_id', table=tbl, name="user_id")
+        old_cons.drop()
+    elif migrate_engine.name in ['postgresql']:
+        old_cons = UniqueConstraint('user_id', 'repository_id', table=tbl)
+        old_cons.drop()
+    else:
+        # sqlite doesn't support dropping constraints...
+        print """Please manually drop UniqueConstraint('user_id', 'repository_id')"""
+
+    #==========================================================================
+    # fix uniques of table `user_repo_group_to_perm`
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UserRepoGroupToPerm
+    tbl = UserRepoGroupToPerm().__table__
+    new_cons = UniqueConstraint('group_id', 'permission_id', 'user_id', table=tbl)
+    new_cons.create()
+
+    # fix uniqueConstraints
+    if migrate_engine.name in ['mysql']:
+        #mysql is givinig troubles here...
+        old_cons = UniqueConstraint('group_id', 'permission_id', table=tbl, name="group_id")
+        old_cons.drop()
+    elif migrate_engine.name in ['postgresql']:
+        old_cons = UniqueConstraint('group_id', 'permission_id', table=tbl, name='group_to_perm_group_id_permission_id_key')
+        old_cons.drop()
+    else:
+        # sqlite doesn't support dropping constraints...
+        print """Please manually drop UniqueConstraint('group_id', 'permission_id')"""
+
+    return
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
--- a/rhodecode/lib/dbmigrate/versions/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/dbmigrate/versions/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Dec 11, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/diffs.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,517 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.lib.diffs
+    ~~~~~~~~~~~~~~~~~~~
+
+    Set of diffing helpers, previously part of vcs
+
+
+    :created_on: Dec 4, 2011
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
+    :original copyright: 2007-2008 by Armin Ronacher
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (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
+import difflib
+import markupsafe
+from itertools import tee, imap
+
+from pylons.i18n.translation import _
+
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.nodes import FileNode
+
+from rhodecode.lib.utils import EmptyChangeset
+
+
+def wrap_to_table(str_):
+    return '''<table class="code-difftable">
+                <tr class="line no-comment">
+                <td class="lineno new"></td>
+                <td class="code no-comment"><pre>%s</pre></td>
+                </tr>
+              </table>''' % str_
+
+
+def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
+                ignore_whitespace=True, line_context=3,
+                enable_comments=False):
+    """
+    returns a wrapped diff into a table, checks for cut_off_limit and presents
+    proper message
+    """
+
+    if filenode_old is None:
+        filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
+
+    if filenode_old.is_binary or filenode_new.is_binary:
+        diff = wrap_to_table(_('binary file'))
+        stats = (0, 0)
+        size = 0
+
+    elif cut_off_limit != -1 and (cut_off_limit is None or
+    (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
+
+        f_gitdiff = get_gitdiff(filenode_old, filenode_new,
+                                ignore_whitespace=ignore_whitespace,
+                                context=line_context)
+        diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
+
+        diff = diff_processor.as_html(enable_comments=enable_comments)
+        stats = diff_processor.stat()
+        size = len(diff or '')
+    else:
+        diff = wrap_to_table(_('Changeset was to big and was cut off, use '
+                               'diff menu to display this diff'))
+        stats = (0, 0)
+        size = 0
+
+    if not diff:
+        diff = wrap_to_table(_('No changes detected'))
+
+    cs1 = filenode_old.last_changeset.raw_id
+    cs2 = filenode_new.last_changeset.raw_id
+
+    return size, cs1, cs2, diff, stats
+
+
+def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
+    """
+    Returns git style diff between given ``filenode_old`` and ``filenode_new``.
+
+    :param ignore_whitespace: ignore whitespaces in diff
+    """
+    # make sure we pass in default context
+    context = context or 3
+
+    for filenode in (filenode_old, filenode_new):
+        if not isinstance(filenode, FileNode):
+            raise VCSError("Given object should be FileNode object, not %s"
+                % filenode.__class__)
+
+    repo = filenode_new.changeset.repository
+    old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
+    new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
+
+    vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
+                                 ignore_whitespace, context)
+
+    return vcs_gitdiff
+
+
+class DiffProcessor(object):
+    """
+    Give it a unified diff and it returns a list of the files that were
+    mentioned in the diff together with a dict of meta information that
+    can be used to render it in a HTML template.
+    """
+    _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
+
+    def __init__(self, diff, differ='diff', format='udiff'):
+        """
+        :param diff:   a text in diff format or generator
+        :param format: format of diff passed, `udiff` or `gitdiff`
+        """
+        if isinstance(diff, basestring):
+            diff = [diff]
+
+        self.__udiff = diff
+        self.__format = format
+        self.adds = 0
+        self.removes = 0
+
+        if isinstance(self.__udiff, basestring):
+            self.lines = iter(self.__udiff.splitlines(1))
+
+        elif self.__format == 'gitdiff':
+            udiff_copy = self.copy_iterator()
+            self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
+        else:
+            udiff_copy = self.copy_iterator()
+            self.lines = imap(self.escaper, udiff_copy)
+
+        # Select a differ.
+        if differ == 'difflib':
+            self.differ = self._highlight_line_difflib
+        else:
+            self.differ = self._highlight_line_udiff
+
+    def escaper(self, string):
+        return markupsafe.escape(string)
+
+    def copy_iterator(self):
+        """
+        make a fresh copy of generator, we should not iterate thru
+        an original as it's needed for repeating operations on
+        this instance of DiffProcessor
+        """
+        self.__udiff, iterator_copy = tee(self.__udiff)
+        return iterator_copy
+
+    def _extract_rev(self, line1, line2):
+        """
+        Extract the filename and revision hint from a line.
+        """
+
+        try:
+            if line1.startswith('--- ') and line2.startswith('+++ '):
+                l1 = line1[4:].split(None, 1)
+                old_filename = (l1[0].replace('a/', '', 1)
+                                if len(l1) >= 1 else None)
+                old_rev = l1[1] if len(l1) == 2 else 'old'
+
+                l2 = line2[4:].split(None, 1)
+                new_filename = (l2[0].replace('b/', '', 1)
+                                if len(l1) >= 1 else None)
+                new_rev = l2[1] if len(l2) == 2 else 'new'
+
+                filename = (old_filename
+                            if old_filename != '/dev/null' else new_filename)
+
+                return filename, new_rev, old_rev
+        except (ValueError, IndexError):
+            pass
+
+        return None, None, None
+
+    def _parse_gitdiff(self, diffiterator):
+        def line_decoder(l):
+            if l.startswith('+') and not l.startswith('+++'):
+                self.adds += 1
+            elif l.startswith('-') and not l.startswith('---'):
+                self.removes += 1
+            return l.decode('utf8', 'replace')
+
+        output = list(diffiterator)
+        size = len(output)
+
+        if size == 2:
+            l = []
+            l.extend([output[0]])
+            l.extend(output[1].splitlines(1))
+            return map(line_decoder, l)
+        elif size == 1:
+            return  map(line_decoder, output[0].splitlines(1))
+        elif size == 0:
+            return []
+
+        raise Exception('wrong size of diff %s' % size)
+
+    def _highlight_line_difflib(self, line, next_):
+        """
+        Highlight inline changes in both lines.
+        """
+
+        if line['action'] == 'del':
+            old, new = line, next_
+        else:
+            old, new = next_, line
+
+        oldwords = re.split(r'(\W)', old['line'])
+        newwords = re.split(r'(\W)', new['line'])
+
+        sequence = difflib.SequenceMatcher(None, oldwords, newwords)
+
+        oldfragments, newfragments = [], []
+        for tag, i1, i2, j1, j2 in sequence.get_opcodes():
+            oldfrag = ''.join(oldwords[i1:i2])
+            newfrag = ''.join(newwords[j1:j2])
+            if tag != 'equal':
+                if oldfrag:
+                    oldfrag = '<del>%s</del>' % oldfrag
+                if newfrag:
+                    newfrag = '<ins>%s</ins>' % newfrag
+            oldfragments.append(oldfrag)
+            newfragments.append(newfrag)
+
+        old['line'] = "".join(oldfragments)
+        new['line'] = "".join(newfragments)
+
+    def _highlight_line_udiff(self, line, next_):
+        """
+        Highlight inline changes in both lines.
+        """
+        start = 0
+        limit = min(len(line['line']), len(next_['line']))
+        while start < limit and line['line'][start] == next_['line'][start]:
+            start += 1
+        end = -1
+        limit -= start
+        while -end <= limit and line['line'][end] == next_['line'][end]:
+            end -= 1
+        end += 1
+        if start or end:
+            def do(l):
+                last = end + len(l['line'])
+                if l['action'] == 'add':
+                    tag = 'ins'
+                else:
+                    tag = 'del'
+                l['line'] = '%s<%s>%s</%s>%s' % (
+                    l['line'][:start],
+                    tag,
+                    l['line'][start:last],
+                    tag,
+                    l['line'][last:]
+                )
+            do(line)
+            do(next_)
+
+    def _parse_udiff(self):
+        """
+        Parse the diff an return data for the template.
+        """
+        lineiter = self.lines
+        files = []
+        try:
+            line = lineiter.next()
+            # skip first context
+            skipfirst = True
+            while 1:
+                # continue until we found the old file
+                if not line.startswith('--- '):
+                    line = lineiter.next()
+                    continue
+
+                chunks = []
+                filename, old_rev, new_rev = \
+                    self._extract_rev(line, lineiter.next())
+                files.append({
+                    'filename':         filename,
+                    'old_revision':     old_rev,
+                    'new_revision':     new_rev,
+                    'chunks':           chunks
+                })
+
+                line = lineiter.next()
+                while line:
+                    match = self._chunk_re.match(line)
+                    if not match:
+                        break
+
+                    lines = []
+                    chunks.append(lines)
+
+                    old_line, old_end, new_line, new_end = \
+                        [int(x or 1) for x in match.groups()[:-1]]
+                    old_line -= 1
+                    new_line -= 1
+                    context = len(match.groups()) == 5
+                    old_end += old_line
+                    new_end += new_line
+
+                    if context:
+                        if not skipfirst:
+                            lines.append({
+                                'old_lineno': '...',
+                                'new_lineno': '...',
+                                'action':     'context',
+                                'line':       line,
+                            })
+                        else:
+                            skipfirst = False
+
+                    line = lineiter.next()
+                    while old_line < old_end or new_line < new_end:
+                        if line:
+                            command, line = line[0], line[1:]
+                        else:
+                            command = ' '
+                        affects_old = affects_new = False
+
+                        # ignore those if we don't expect them
+                        if command in '#@':
+                            continue
+                        elif command == '+':
+                            affects_new = True
+                            action = 'add'
+                        elif command == '-':
+                            affects_old = True
+                            action = 'del'
+                        else:
+                            affects_old = affects_new = True
+                            action = 'unmod'
+
+                        old_line += affects_old
+                        new_line += affects_new
+                        lines.append({
+                            'old_lineno':   affects_old and old_line or '',
+                            'new_lineno':   affects_new and new_line or '',
+                            'action':       action,
+                            'line':         line
+                        })
+                        line = lineiter.next()
+
+        except StopIteration:
+            pass
+
+        # highlight inline changes
+        for _ in files:
+            for chunk in chunks:
+                lineiter = iter(chunk)
+                #first = True
+                try:
+                    while 1:
+                        line = lineiter.next()
+                        if line['action'] != 'unmod':
+                            nextline = lineiter.next()
+                            if nextline['action'] == 'unmod' or \
+                               nextline['action'] == line['action']:
+                                continue
+                            self.differ(line, nextline)
+                except StopIteration:
+                    pass
+
+        return files
+
+    def prepare(self):
+        """
+        Prepare the passed udiff for HTML rendering. It'l return a list
+        of dicts
+        """
+        return self._parse_udiff()
+
+    def _safe_id(self, idstring):
+        """Make a string safe for including in an id attribute.
+
+        The HTML spec says that id attributes 'must begin with
+        a letter ([A-Za-z]) and may be followed by any number
+        of letters, digits ([0-9]), hyphens ("-"), underscores
+        ("_"), colons (":"), and periods (".")'. These regexps
+        are slightly over-zealous, in that they remove colons
+        and periods unnecessarily.
+
+        Whitespace is transformed into underscores, and then
+        anything which is not a hyphen or a character that
+        matches \w (alphanumerics and underscore) is removed.
+
+        """
+        # Transform all whitespace to underscore
+        idstring = re.sub(r'\s', "_", '%s' % idstring)
+        # Remove everything that is not a hyphen or a member of \w
+        idstring = re.sub(r'(?!-)\W', "", idstring).lower()
+        return idstring
+
+    def raw_diff(self):
+        """
+        Returns raw string as udiff
+        """
+        udiff_copy = self.copy_iterator()
+        if self.__format == 'gitdiff':
+            udiff_copy = self._parse_gitdiff(udiff_copy)
+        return u''.join(udiff_copy)
+
+    def as_html(self, table_class='code-difftable', line_class='line',
+                new_lineno_class='lineno old', old_lineno_class='lineno new',
+                code_class='code', enable_comments=False):
+        """
+        Return udiff as html table with customized css classes
+        """
+        def _link_to_if(condition, label, url):
+            """
+            Generates a link if condition is meet or just the label if not.
+            """
+
+            if condition:
+                return '''<a href="%(url)s">%(label)s</a>''' % {
+                    'url': url,
+                    'label': label
+                }
+            else:
+                return label
+        diff_lines = self.prepare()
+        _html_empty = True
+        _html = []
+        _html.append('''<table class="%(table_class)s">\n''' % {
+            'table_class': table_class
+        })
+        for diff in diff_lines:
+            for line in diff['chunks']:
+                _html_empty = False
+                for change in line:
+                    _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
+                        'lc': line_class,
+                        'action': change['action']
+                    })
+                    anchor_old_id = ''
+                    anchor_new_id = ''
+                    anchor_old = "%(filename)s_o%(oldline_no)s" % {
+                        'filename': self._safe_id(diff['filename']),
+                        'oldline_no': change['old_lineno']
+                    }
+                    anchor_new = "%(filename)s_n%(oldline_no)s" % {
+                        'filename': self._safe_id(diff['filename']),
+                        'oldline_no': change['new_lineno']
+                    }
+                    cond_old = (change['old_lineno'] != '...' and
+                                change['old_lineno'])
+                    cond_new = (change['new_lineno'] != '...' and
+                                change['new_lineno'])
+                    if cond_old:
+                        anchor_old_id = 'id="%s"' % anchor_old
+                    if cond_new:
+                        anchor_new_id = 'id="%s"' % anchor_new
+                    ###########################################################
+                    # OLD LINE NUMBER
+                    ###########################################################
+                    _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
+                        'a_id': anchor_old_id,
+                        'olc': old_lineno_class
+                    })
+
+                    _html.append('''%(link)s''' % {
+                        'link': _link_to_if(True, change['old_lineno'],
+                                            '#%s' % anchor_old)
+                    })
+                    _html.append('''</td>\n''')
+                    ###########################################################
+                    # NEW LINE NUMBER
+                    ###########################################################
+
+                    _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
+                        'a_id': anchor_new_id,
+                        'nlc': new_lineno_class
+                    })
+
+                    _html.append('''%(link)s''' % {
+                        'link': _link_to_if(True, change['new_lineno'],
+                                            '#%s' % anchor_new)
+                    })
+                    _html.append('''</td>\n''')
+                    ###########################################################
+                    # CODE
+                    ###########################################################
+                    comments = '' if enable_comments else 'no-comment'
+                    _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
+                        'cc': code_class,
+                        'inc': comments
+                    })
+                    _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
+                        'code': change['line']
+                    })
+                    _html.append('''\t</td>''')
+                    _html.append('''\n</tr>\n''')
+        _html.append('''</table>''')
+        if _html_empty:
+            return None
+        return ''.join(_html)
+
+    def stat(self):
+        """
+        Returns tuple of added, and removed lines for this instance
+        """
+        return self.adds, self.removes
--- a/rhodecode/lib/exceptions.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/exceptions.py	Sun Feb 26 17:25:09 2012 +0200
@@ -6,7 +6,8 @@
     Set of custom exceptions used in RhodeCode
 
     :created_on: Nov 17, 2010
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -46,5 +47,6 @@
 class UserOwnsReposException(Exception):
     pass
 
+
 class UsersGroupsAssignedException(Exception):
     pass
--- a/rhodecode/lib/helpers.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/helpers.py	Sun Feb 26 17:25:09 2012 +0200
@@ -8,22 +8,24 @@
 import StringIO
 import urllib
 import math
+import logging
 
 from datetime import datetime
-from pygments.formatters import HtmlFormatter
+from pygments.formatters.html import HtmlFormatter
 from pygments import highlight as code_highlight
 from pylons import url, request, config
 from pylons.i18n.translation import _, ungettext
+from hashlib import md5
 
 from webhelpers.html import literal, HTML, escape
 from webhelpers.html.tools import *
 from webhelpers.html.builder import make_tag
 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
-    end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
-    link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
-    password, textarea, title, ul, xml_declaration, radio
-from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
-    mail_to, strip_links, strip_tags, tag_re
+    end_form, file, form, hidden, image, javascript_link, link_to, \
+    link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
+    submit, text, password, textarea, title, ul, xml_declaration, radio
+from webhelpers.html.tools import auto_link, button_to, highlight, \
+    js_obfuscate, mail_to, strip_links, strip_tags, tag_re
 from webhelpers.number import format_byte_size, format_bit_size
 from webhelpers.pylonslib import Flash as _Flash
 from webhelpers.pylonslib.secure_form import secure_form
@@ -33,11 +35,15 @@
 from webhelpers.date import time_ago_in_words
 from webhelpers.paginate import Page
 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
-    convert_boolean_attrs, NotGiven
+    convert_boolean_attrs, NotGiven, _make_safe_id_component
 
-from vcs.utils.annotate import annotate_highlight
+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 import str2bool, safe_unicode, safe_str, get_changeset_safe
+from rhodecode.lib.markup_renderer import MarkupRenderer
+
+log = logging.getLogger(__name__)
+
 
 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
     """
@@ -49,6 +55,19 @@
     return HTML.input(**attrs)
 
 reset = _reset
+safeid = _make_safe_id_component
+
+
+def FID(raw_id, path):
+    """
+    Creates a uniqe ID for filenode based on it's hash of path and revision
+    it's safe to use in urls
+
+    :param raw_id:
+    :param path:
+    """
+
+    return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
 
 
 def get_token():
@@ -104,10 +123,14 @@
         paths_l = paths.split('/')
         for cnt, p in enumerate(paths_l):
             if p != '':
-                url_l.append(link_to(p, url('files_home',
-                                            repo_name=repo_name,
-                                            revision=rev,
-                                            f_path='/'.join(paths_l[:cnt + 1]))))
+                url_l.append(link_to(p,
+                                     url('files_home',
+                                         repo_name=repo_name,
+                                         revision=rev,
+                                         f_path='/'.join(paths_l[:cnt + 1])
+                                         )
+                                     )
+                             )
 
         return literal('/'.join(url_l))
 
@@ -198,13 +221,16 @@
     return literal(code_highlight(filenode.content,
                                   filenode.lexer, CodeHtmlFormatter(**kwargs)))
 
+
 def pygmentize_annotation(repo_name, filenode, **kwargs):
-    """pygmentize function for annotation
+    """
+    pygmentize function for annotation
 
     :param filenode:
     """
 
     color_dict = {}
+
     def gen_color(n=10000):
         """generator for getting n of evenly distributed colors using
         hsv color and golden ratio. It always return same order of colors
@@ -213,19 +239,26 @@
         """
 
         def hsv_to_rgb(h, s, v):
-            if s == 0.0: return v, v, v
-            i = int(h * 6.0) # XXX assume int() truncates!
+            if s == 0.0:
+                return v, v, v
+            i = int(h * 6.0)  # XXX assume int() truncates!
             f = (h * 6.0) - i
             p = v * (1.0 - s)
             q = v * (1.0 - s * f)
             t = v * (1.0 - s * (1.0 - f))
             i = i % 6
-            if i == 0: return v, t, p
-            if i == 1: return q, v, p
-            if i == 2: return p, v, t
-            if i == 3: return p, q, v
-            if i == 4: return t, p, v
-            if i == 5: return v, p, q
+            if i == 0:
+                return v, t, p
+            if i == 1:
+                return q, v, p
+            if i == 2:
+                return p, v, t
+            if i == 3:
+                return p, q, v
+            if i == 4:
+                return t, p, v
+            if i == 5:
+                return v, p, q
 
         golden_ratio = 0.618033988749895
         h = 0.22717784590367374
@@ -235,12 +268,12 @@
             h %= 1
             HSV_tuple = [h, 0.95, 0.95]
             RGB_tuple = hsv_to_rgb(*HSV_tuple)
-            yield map(lambda x:str(int(x * 256)), RGB_tuple)
+            yield map(lambda x: str(int(x * 256)), RGB_tuple)
 
     cgenerator = gen_color()
 
     def get_color_string(cs):
-        if color_dict.has_key(cs):
+        if cs in color_dict:
             col = color_dict[cs]
         else:
             col = color_dict[cs] = cgenerator.next()
@@ -275,6 +308,7 @@
 
     return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
 
+
 def is_following_repo(repo_name, user_id):
     from rhodecode.model.scm import ScmModel
     return ScmModel().is_following_repo(repo_name, user_id)
@@ -284,17 +318,75 @@
 #==============================================================================
 # SCM FILTERS available via h.
 #==============================================================================
-from vcs.utils import author_name, author_email
+from rhodecode.lib.vcs.utils import author_name, author_email
 from rhodecode.lib import credentials_filter, age as _age
+from rhodecode.model.db import User
 
-age = lambda  x:_age(x)
+age = lambda  x: _age(x)
 capitalize = lambda x: x.capitalize()
 email = author_email
-email_or_none = lambda x: email(x) if email(x) != x else None
-person = lambda x: author_name(x)
 short_id = lambda x: x[:12]
 hide_credentials = lambda x: ''.join(credentials_filter(x))
 
+
+def is_git(repository):
+    if hasattr(repository, 'alias'):
+        _type = repository.alias
+    elif hasattr(repository, 'repo_type'):
+        _type = repository.repo_type
+    else:
+        _type = repository
+    return _type == 'git'
+
+
+def is_hg(repository):
+    if hasattr(repository, 'alias'):
+        _type = repository.alias
+    elif hasattr(repository, 'repo_type'):
+        _type = repository.repo_type
+    else:
+        _type = repository
+    return _type == 'hg'
+
+
+def email_or_none(author):
+    _email = email(author)
+    if _email != '':
+        return _email
+
+    # See if it contains a username we can get an email from
+    user = User.get_by_username(author_name(author), case_insensitive=True,
+                                cache=True)
+    if user is not None:
+        return user.email
+
+    # No valid email, not a valid user in the system, none!
+    return None
+
+
+def person(author):
+    # attr to return from fetched user
+    person_getter = lambda usr: usr.username
+
+    # Valid email in the attribute passed, see if they're in the system
+    _email = email(author)
+    if _email != '':
+        user = User.get_by_email(_email, case_insensitive=True, cache=True)
+        if user is not None:
+            return person_getter(user)
+        return _email
+
+    # Maybe it's a username?
+    _author = author_name(author)
+    user = User.get_by_username(_author, case_insensitive=True,
+                                cache=True)
+    if user is not None:
+        return person_getter(user)
+
+    # Still nothing?  Just pass back the author name then
+    return _author
+
+
 def bool2icon(value):
     """Returns True/False values represented as small html image of true/false
     icons
@@ -314,7 +406,8 @@
 
 
 def action_parser(user_log, feed=False):
-    """This helper will action_map the specified string action into translated
+    """
+    This helper will action_map the specified string action into translated
     fancy names with icons and links
 
     :param user_log: user log instance
@@ -330,52 +423,84 @@
         action, action_params = x
 
     def get_cs_links():
-        revs_limit = 3 #display this amount always
-        revs_top_limit = 50 #show upto this amount of changesets hidden
-        revs = action_params.split(',')
+        revs_limit = 3  # display this amount always
+        revs_top_limit = 50  # show upto this amount of changesets hidden
+        revs_ids = action_params.split(',')
+        deleted = user_log.repository is None
+        if deleted:
+            return ','.join(revs_ids)
+
         repo_name = user_log.repository.repo_name
 
-        from rhodecode.model.scm import ScmModel
         repo = user_log.repository.scm_instance
 
-        message = lambda rev: get_changeset_safe(repo, rev).message
+        message = lambda rev: rev.message
+        lnk = lambda rev, repo_name: (
+            link_to('r%s:%s' % (rev.revision, rev.short_id),
+                    url('changeset_home', repo_name=repo_name,
+                        revision=rev.raw_id),
+                    title=tooltip(message(rev)), class_='tooltip')
+        )
+        # get only max revs_top_limit of changeset for performance/ui reasons
+        revs = [
+            x for x in repo.get_changesets(revs_ids[0],
+                                           revs_ids[:revs_top_limit][-1])
+        ]
+
         cs_links = []
-        cs_links.append(" " + ', '.join ([link_to(rev,
-                url('changeset_home',
-                repo_name=repo_name,
-                revision=rev), title=tooltip(message(rev)),
-                class_='tooltip') for rev in revs[:revs_limit] ]))
+        cs_links.append(" " + ', '.join(
+            [lnk(rev, repo_name) for rev in revs[:revs_limit]]
+            )
+        )
 
-        compare_view = (' <div class="compare_view tooltip" title="%s">'
-                        '<a href="%s">%s</a> '
-                        '</div>' % (_('Show all combined changesets %s->%s' \
-                                      % (revs[0], revs[-1])),
-                                    url('changeset_home', repo_name=repo_name,
-                                        revision='%s...%s' % (revs[0], revs[-1])
-                                    ),
-                                    _('compare view'))
-                        )
+        compare_view = (
+            ' <div class="compare_view tooltip" title="%s">'
+            '<a href="%s">%s</a> </div>' % (
+                _('Show all combined changesets %s->%s') % (
+                    revs_ids[0], revs_ids[-1]
+                ),
+                url('changeset_home', repo_name=repo_name,
+                    revision='%s...%s' % (revs_ids[0], revs_ids[-1])
+                ),
+                _('compare view')
+            )
+        )
 
-        if len(revs) > revs_limit:
-            uniq_id = revs[0]
-            html_tmpl = ('<span> %s '
-            '<a class="show_more" id="_%s" href="#more">%s</a> '
-            '%s</span>')
+        # if we have exactly one more than normally displayed
+        # just display it, takes less space than displaying
+        # "and 1 more revisions"
+        if len(revs_ids) == revs_limit + 1:
+            rev = revs[revs_limit]
+            cs_links.append(", " + lnk(rev, repo_name))
+
+        # hidden-by-default ones
+        if len(revs_ids) > revs_limit + 1:
+            uniq_id = revs_ids[0]
+            html_tmpl = (
+                '<span> %s <a class="show_more" id="_%s" '
+                'href="#more">%s</a> %s</span>'
+            )
             if not feed:
-                cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
-                                        % (len(revs) - revs_limit),
-                                        _('revisions')))
+                cs_links.append(html_tmpl % (
+                      _('and'),
+                      uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
+                      _('revisions')
+                    )
+                )
 
             if not feed:
-                html_tmpl = '<span id="%s" style="display:none"> %s </span>'
+                html_tmpl = '<span id="%s" style="display:none">, %s </span>'
             else:
                 html_tmpl = '<span id="%s"> %s </span>'
 
-            cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
-                url('changeset_home',
-                repo_name=repo_name, revision=rev),
-                title=message(rev), class_='tooltip')
-                for rev in revs[revs_limit:revs_top_limit]])))
+            morelinks = ', '.join(
+              [lnk(rev, repo_name) for rev in revs[revs_limit:]]
+            )
+
+            if len(revs_ids) > revs_top_limit:
+                morelinks += ', ...'
+
+            cs_links.append(html_tmpl % (uniq_id, morelinks))
         if len(revs) > 1:
             cs_links.append(compare_view)
         return ''.join(cs_links)
@@ -385,36 +510,39 @@
         return _('fork name ') + str(link_to(action_params, url('summary_home',
                                           repo_name=repo_name,)))
 
-    action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
-           'user_created_repo':(_('[created] repository'), None),
-           'user_forked_repo':(_('[forked] repository'), get_fork_name),
-           'user_updated_repo':(_('[updated] repository'), None),
-           'admin_deleted_repo':(_('[delete] repository'), None),
-           'admin_created_repo':(_('[created] repository'), None),
-           'admin_forked_repo':(_('[forked] repository'), None),
-           'admin_updated_repo':(_('[updated] repository'), None),
-           'push':(_('[pushed] into'), get_cs_links),
-           'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
-           'push_remote':(_('[pulled from remote] into'), get_cs_links),
-           'pull':(_('[pulled] from'), None),
-           'started_following_repo':(_('[started following] repository'), None),
-           'stopped_following_repo':(_('[stopped following] repository'), None),
+    action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
+           'user_created_repo': (_('[created] repository'), None),
+           'user_created_fork': (_('[created] repository as fork'), None),
+           'user_forked_repo': (_('[forked] repository'), get_fork_name),
+           'user_updated_repo': (_('[updated] repository'), None),
+           'admin_deleted_repo': (_('[delete] repository'), None),
+           'admin_created_repo': (_('[created] repository'), None),
+           'admin_forked_repo': (_('[forked] repository'), None),
+           'admin_updated_repo': (_('[updated] repository'), None),
+           'push': (_('[pushed] into'), get_cs_links),
+           'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
+           'push_remote': (_('[pulled from remote] into'), get_cs_links),
+           'pull': (_('[pulled] from'), None),
+           'started_following_repo': (_('[started following] repository'), None),
+           'stopped_following_repo': (_('[stopped following] repository'), None),
             }
 
     action_str = action_map.get(action, action)
     if feed:
         action = action_str[0].replace('[', '').replace(']', '')
     else:
-        action = action_str[0].replace('[', '<span class="journal_highlight">')\
-                   .replace(']', '</span>')
+        action = action_str[0]\
+            .replace('[', '<span class="journal_highlight">')\
+            .replace(']', '</span>')
 
-    action_params_func = lambda :""
+    action_params_func = lambda: ""
 
     if callable(action_str[1]):
         action_params_func = action_str[1]
 
     return [literal(action), action_params_func]
 
+
 def action_parser_icon(user_log):
     action = user_log.action
     action_params = None
@@ -426,6 +554,7 @@
     tmpl = """<img src="%s%s" alt="%s"/>"""
     map = {'user_deleted_repo':'database_delete.png',
            'user_created_repo':'database_add.png',
+           'user_created_fork':'arrow_divide.png',
            'user_forked_repo':'arrow_divide.png',
            'user_updated_repo':'database_edit.png',
            'admin_deleted_repo':'database_delete.png',
@@ -449,6 +578,7 @@
 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
 HasRepoPermissionAny, HasRepoPermissionAll
 
+
 #==============================================================================
 # GRAVATAR URL
 #==============================================================================
@@ -456,7 +586,8 @@
 def gravatar_url(email_address, size=30):
     if (not str2bool(config['app_conf'].get('use_gravatar')) or
         not email_address or email_address == 'anonymous@rhodecode.org'):
-        return url("/images/user%s.png" % size)
+        f = lambda a, l: min(l, key=lambda x: abs(x - a))
+        return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
 
     ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
     default = 'identicon'
@@ -469,7 +600,7 @@
         email_address = safe_str(email_address)
     # construct the url
     gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
-    gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
+    gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
 
     return gravatar_url
 
@@ -480,7 +611,7 @@
 class RepoPage(Page):
 
     def __init__(self, collection, page=1, items_per_page=20,
-        item_count=None, url=None, branch_name=None, **kwargs):
+                 item_count=None, url=None, **kwargs):
 
         """Create a "RepoPage" instance. special pager for paging
         repository
@@ -498,7 +629,7 @@
         # The self.page is the number of the current page.
         # The first page has the number 1!
         try:
-            self.page = int(page) # make it int() if we get it as a string
+            self.page = int(page)  # make it int() if we get it as a string
         except (ValueError, TypeError):
             self.page = 1
 
@@ -518,7 +649,8 @@
                                             self.items_per_page))
             self.last_page = self.first_page + self.page_count - 1
 
-            # Make sure that the requested page number is the range of valid pages
+            # Make sure that the requested page number is the range of
+            # valid pages
             if self.page > self.last_page:
                 self.page = self.last_page
             elif self.page < self.first_page:
@@ -531,11 +663,7 @@
             self.last_item = ((self.item_count - 1) - items_per_page *
                               (self.page - 1))
 
-            iterator = self.collection.get_changesets(start=self.first_item,
-                                                      end=self.last_item,
-                                                      reverse=True,
-                                                      branch_name=branch_name)
-            self.items = list(iterator)
+            self.items = list(self.collection[self.first_item:self.last_item + 1])
 
             # Links to previous and next page
             if self.page > self.first_page:
@@ -560,14 +688,14 @@
             self.items = []
 
         # This is a subclass of the 'list' type. Initialise the list now.
-        list.__init__(self, self.items)
+        list.__init__(self, reversed(self.items))
 
 
 def changed_tooltip(nodes):
     """
     Generates a html string for changed nodes in changeset page.
     It limits the output to 30 entries
-    
+
     :param nodes: LazyNodesGenerator
     """
     if nodes:
@@ -581,15 +709,14 @@
         return ': ' + _('No Files')
 
 
-
 def repo_link(groups_and_repos):
     """
     Makes a breadcrumbs link to repo within a group
     joins &raquo; on each group to create a fancy link
-    
+
     ex::
         group >> subgroup >> repo
-    
+
     :param groups_and_repos:
     """
     groups, repo_name = groups_and_repos
@@ -603,11 +730,12 @@
         return literal(' &raquo; '.join(map(make_link, groups)) + \
                        " &raquo; " + repo_name)
 
+
 def fancy_file_stats(stats):
     """
     Displays a fancy two colored bar for number of added/deleted
     lines of code on file
-    
+
     :param stats: two element list of added/deleted lines of code
     """
 
@@ -630,13 +758,12 @@
     a_v = a if a > 0 else ''
     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'}
-        map_getter = lambda x:mapping[x]
+        mapping = {'tr': 'top-right-rounded-corner',
+                   'tl': 'top-left-rounded-corner',
+                   'br': 'bottom-right-rounded-corner',
+                   'bl': 'bottom-left-rounded-corner'}
+        map_getter = lambda x: mapping[x]
 
         if l_type == 'a' and d_v:
             #case when added and deleted are present
@@ -651,22 +778,137 @@
         if l_type == 'd' and not a_v:
             return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
 
-
-
-    d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
-                                                                 a_p, a_v)
-    d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
-                                                                   d_p, d_v)
+    d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
+        cgen('a'), a_p, a_v
+    )
+    d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
+        cgen('d'), d_p, d_v
+    )
     return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
 
 
-def urlify_text(text):
+def urlify_text(text_):
     import re
 
-    url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
+    url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
+                         '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
 
     def url_func(match_obj):
         url_full = match_obj.groups()[0]
-        return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
+        return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
+
+    return literal(url_pat.sub(url_func, text_))
+
+
+def urlify_changesets(text_, repository):
+    import re
+    URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
+
+    def url_func(match_obj):
+        rev = match_obj.groups()[0]
+        pref = ''
+        if match_obj.group().startswith(' '):
+            pref = ' '
+        tmpl = (
+        '%(pref)s<a class="%(cls)s" href="%(url)s">'
+        '%(rev)s'
+        '</a>'
+        )
+        return tmpl % {
+         'pref': pref,
+         'cls': 'revision-link',
+         'url': url('changeset_home', repo_name=repository, revision=rev),
+         'rev': rev,
+        }
+
+    newtext = URL_PAT.sub(url_func, text_)
+
+    return newtext
+
+
+def urlify_commit(text_, repository=None, link_=None):
+    """
+    Parses given text message and makes proper links.
+    issues are linked to given issue-server, and rest is a changeset link
+    if link_ is given, in other case it's a plain text
+
+    :param text_:
+    :param repository:
+    :param link_: changeset link
+    """
+    import re
+    import traceback
+
+    # urlify changesets
+    text_ = urlify_changesets(text_, repository)
+
+    def linkify_others(t, l):
+        urls = re.compile(r'(\<a.*?\<\/a\>)',)
+        links = []
+        for e in urls.split(t):
+            if not urls.match(e):
+                links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
+            else:
+                links.append(e)
 
-    return literal(url_pat.sub(url_func, text))
+        return ''.join(links)
+    try:
+        conf = config['app_conf']
+
+        URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
+
+        if URL_PAT:
+            ISSUE_SERVER_LNK = conf.get('issue_server_link')
+            ISSUE_PREFIX = conf.get('issue_prefix')
+
+            def url_func(match_obj):
+                pref = ''
+                if match_obj.group().startswith(' '):
+                    pref = ' '
+
+                issue_id = ''.join(match_obj.groups())
+                tmpl = (
+                '%(pref)s<a class="%(cls)s" href="%(url)s">'
+                '%(issue-prefix)s%(id-repr)s'
+                '</a>'
+                )
+                url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
+                if repository:
+                    url = url.replace('{repo}', repository)
+
+                return tmpl % {
+                     'pref': pref,
+                     'cls': 'issue-tracker-link',
+                     'url': url,
+                     'id-repr': issue_id,
+                     'issue-prefix': ISSUE_PREFIX,
+                     'serv': ISSUE_SERVER_LNK,
+                }
+
+            newtext = URL_PAT.sub(url_func, text_)
+
+            if link_:
+                # wrap not links into final link => link_
+                newtext = linkify_others(newtext, link_)
+
+            return literal(newtext)
+    except:
+        log.error(traceback.format_exc())
+        pass
+
+    return text_
+
+
+def rst(source):
+    return literal('<div class="rst-block">%s</div>' %
+                   MarkupRenderer.rst(source))
+
+
+def rst_w_mentions(source):
+    """
+    Wrapped rst renderer with @mention highlighting
+
+    :param source:
+    """
+    return literal('<div class="rst-block">%s</div>' %
+                   MarkupRenderer.rst_with_mentions(source))
--- a/rhodecode/lib/hooks.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/hooks.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Aug 6, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -33,15 +33,14 @@
 
 
 def repo_size(ui, repo, hooktype=None, **kwargs):
-    """Presents size of repository after push
+    """
+    Presents size of repository after push
 
     :param ui:
     :param repo:
     :param hooktype:
     """
 
-    if hooktype != 'changegroup':
-        return False
     size_hg, size_root = 0, 0
     for path, dirs, files in os.walk(repo.root):
         if path.find('.hg') != -1:
@@ -60,12 +59,20 @@
     size_hg_f = h.format_byte_size(size_hg)
     size_root_f = h.format_byte_size(size_root)
     size_total_f = h.format_byte_size(size_root + size_hg)
-    sys.stdout.write('Repository size .hg:%s repo:%s total:%s\n' \
-                     % (size_hg_f, size_root_f, size_total_f))
+
+    last_cs = repo[len(repo) - 1]
+
+    msg = ('Repository size .hg:%s repo:%s total:%s\n'
+           'Last revision is now r%s:%s\n') % (
+        size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
+    )
+
+    sys.stdout.write(msg)
 
 
 def log_pull_action(ui, repo, **kwargs):
-    """Logs user last pull action
+    """
+    Logs user last pull action
 
     :param ui:
     :param repo:
@@ -76,13 +83,15 @@
     repository = extra_params['repository']
     action = 'pull'
 
-    action_logger(username, action, repository, extra_params['ip'])
+    action_logger(username, action, repository, extra_params['ip'],
+                  commit=True)
 
     return 0
 
 
 def log_push_action(ui, repo, **kwargs):
-    """Maps user last push action to new changeset id, from mercurial
+    """
+    Maps user last push action to new changeset id, from mercurial
 
     :param ui:
     :param repo:
@@ -110,6 +119,37 @@
 
     action = action % ','.join(revs)
 
-    action_logger(username, action, repository, extra_params['ip'])
+    action_logger(username, action, repository, extra_params['ip'],
+                  commit=True)
 
     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
+
+    :param repository: dict dump of repository object
+    :param created_by: username who created repository
+    :param created_date: date of creation
+
+    available keys of repository_dict:
+
+     'repo_type',
+     'description',
+     'private',
+     'created_on',
+     'enable_downloads',
+     'repo_id',
+     'user_id',
+     'enable_statistics',
+     'clone_uri',
+     'fork_id',
+     'group_id',
+     'repo_name'
+
+    """
+
+
+    return 0
--- a/rhodecode/lib/indexers/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/indexers/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Aug 17, 2010
     :author: marcink
-    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -37,38 +37,39 @@
 from whoosh.fields import TEXT, ID, STORED, Schema, FieldType
 from whoosh.index import create_in, open_dir
 from whoosh.formats import Characters
-from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter
+from whoosh.highlight import highlight, HtmlFormatter, ContextFragmenter
 
 from webhelpers.html.builder import escape
 from sqlalchemy import engine_from_config
-from vcs.utils.lazy import LazyProperty
 
 from rhodecode.model import init_model
 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
+from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, LazyProperty
 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
 
-#EXTENSIONS WE WANT TO INDEX CONTENT OFF
+# EXTENSIONS WE WANT TO INDEX CONTENT OFF
 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
 
-#CUSTOM ANALYZER wordsplit + lowercase filter
+# CUSTOM ANALYZER wordsplit + lowercase filter
 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
 
 
 #INDEX SCHEMA DEFINITION
-SCHEMA = Schema(owner=TEXT(),
-                repository=TEXT(stored=True),
-                path=TEXT(stored=True),
-                content=FieldType(format=Characters(ANALYZER),
-                             scorable=True, stored=True),
-                modtime=STORED(), extension=TEXT(stored=True))
-
+SCHEMA = Schema(
+    owner=TEXT(),
+    repository=TEXT(stored=True),
+    path=TEXT(stored=True),
+    content=FieldType(format=Characters(), analyzer=ANALYZER,
+                      scorable=True, stored=True),
+    modtime=STORED(),
+    extension=TEXT(stored=True)
+)
 
 IDX_NAME = 'HG_INDEX'
 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
-FRAGMENTER = SimpleFragmenter(200)
+FRAGMENTER = ContextFragmenter(200)
 
 
 class MakeIndex(BasePasterCommand):
@@ -129,13 +130,14 @@
                                 " destroy old and build from scratch",
                           default=False)
 
+
 class ResultWrapper(object):
     def __init__(self, search_type, searcher, matcher, highlight_items):
         self.search_type = search_type
         self.searcher = searcher
         self.matcher = matcher
         self.highlight_items = highlight_items
-        self.fragment_size = 200 / 2
+        self.fragment_size = 200
 
     @LazyProperty
     def doc_ids(self):
@@ -171,11 +173,10 @@
         """
         i, j = key.start, key.stop
 
-        slice = []
+        slices = []
         for docid in self.doc_ids[i:j]:
-            slice.append(self.get_full_content(docid))
-        return slice
-
+            slices.append(self.get_full_content(docid))
+        return slices
 
     def get_full_content(self, docid):
         res = self.searcher.stored_fields(docid[0])
@@ -183,9 +184,9 @@
                              + len(res['repository']):].lstrip('/')
 
         content_short = self.get_short_content(res, docid[1])
-        res.update({'content_short':content_short,
-                    'content_short_hl':self.highlight(content_short),
-                    'f_path':f_path})
+        res.update({'content_short': content_short,
+                    'content_short_hl': self.highlight(content_short),
+                    'f_path': f_path})
 
         return res
 
@@ -198,7 +199,7 @@
         Smart function that implements chunking the content
         but not overlap chunks so it doesn't highlight the same
         close occurrences twice.
-        
+
         :param matcher:
         :param size:
         """
@@ -217,10 +218,12 @@
     def highlight(self, content, top=5):
         if self.search_type != 'content':
             return ''
-        hl = highlight(escape(content),
-                 self.highlight_items,
-                 analyzer=ANALYZER,
-                 fragmenter=FRAGMENTER,
-                 formatter=FORMATTER,
-                 top=top)
+        hl = highlight(
+            text=escape(content),
+            terms=self.highlight_items,
+            analyzer=ANALYZER,
+            fragmenter=FRAGMENTER,
+            formatter=FORMATTER,
+            top=top
+        )
         return hl
--- a/rhodecode/lib/indexers/daemon.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/indexers/daemon.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Jan 26, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -43,12 +43,12 @@
 from rhodecode.lib import safe_unicode
 from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME
 
-from vcs.exceptions import ChangesetError, RepositoryError
+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)
@@ -67,12 +67,13 @@
 # add ch to logger
 log.addHandler(ch)
 
+
 class WhooshIndexingDaemon(object):
     """
     Daemon for atomic jobs
     """
 
-    def __init__(self, indexname='HG_INDEX', index_location=None,
+    def __init__(self, indexname=IDX_NAME, index_location=None,
                  repo_location=None, sa=None, repo_list=None):
         self.indexname = indexname
 
@@ -94,7 +95,6 @@
 
             self.repo_paths = filtered_repo_paths
 
-
         self.initial = False
         if not os.path.isdir(self.index_location):
             os.makedirs(self.index_location)
@@ -154,7 +154,6 @@
                         modtime=self.get_node_mtime(node),
                         extension=node.extension)
 
-
     def build_index(self):
         if os.path.exists(self.index_location):
             log.debug('removing previous index')
@@ -176,7 +175,6 @@
         writer.commit(merge=True)
         log.debug('>>> FINISHED BUILDING INDEX <<<')
 
-
     def update_index(self):
         log.debug('STARTING INCREMENTAL INDEXING UPDATE')
 
@@ -198,7 +196,7 @@
 
             try:
                 node = self.get_node(repo, indexed_path)
-            except ChangesetError:
+            except (ChangesetError, NodeDoesNotExistError):
                 # This file was deleted since it was indexed
                 log.debug('removing from index %s' % indexed_path)
                 writer.delete_by_term('path', indexed_path)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/markup_renderer.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.lib.markup_renderer
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+    Renderer for markup languages with ability to parse using rst or markdown
+
+    :created_on: Oct 27, 2011
+    :author: marcink
+    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (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
+import logging
+
+from rhodecode.lib import safe_unicode
+
+log = logging.getLogger(__name__)
+
+
+class MarkupRenderer(object):
+    RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
+
+    MARKDOWN_PAT = re.compile(r'md|mkdn?|mdown|markdown', re.IGNORECASE)
+    RST_PAT = re.compile(r're?st', re.IGNORECASE)
+    PLAIN_PAT = re.compile(r'readme', re.IGNORECASE)
+
+    def __detect_renderer(self, source, filename=None):
+        """
+        runs detection of what renderer should be used for generating html
+        from a markup language
+
+        filename can be also explicitly a renderer name
+
+        :param source:
+        :param filename:
+        """
+
+        if MarkupRenderer.MARKDOWN_PAT.findall(filename):
+            detected_renderer = 'markdown'
+        elif MarkupRenderer.RST_PAT.findall(filename):
+            detected_renderer = 'rst'
+        elif MarkupRenderer.PLAIN_PAT.findall(filename):
+            detected_renderer = 'rst'
+        else:
+            detected_renderer = 'plain'
+
+        return getattr(MarkupRenderer, detected_renderer)
+
+    def render(self, source, filename=None):
+        """
+        Renders a given filename using detected renderer
+        it detects renderers based on file extension or mimetype.
+        At last it will just do a simple html replacing new lines with <br/>
+
+        :param file_name:
+        :param source:
+        """
+
+        renderer = self.__detect_renderer(source, filename)
+        readme_data = renderer(source)
+        return readme_data
+
+    @classmethod
+    def plain(cls, source):
+        source = safe_unicode(source)
+
+        def urlify_text(text):
+            url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'
+                                 '|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
+
+            def url_func(match_obj):
+                url_full = match_obj.groups()[0]
+                return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
+
+            return url_pat.sub(url_func, text)
+
+        source = urlify_text(source)
+        return '<br />' + source.replace("\n", '<br />')
+
+    @classmethod
+    def markdown(cls, source):
+        source = safe_unicode(source)
+        try:
+            import markdown as __markdown
+            return __markdown.markdown(source, ['codehilite'])
+        except ImportError:
+            log.warning('Install markdown to use this function')
+            return cls.plain(source)
+
+    @classmethod
+    def rst(cls, source):
+        source = safe_unicode(source)
+        try:
+            from docutils.core import publish_parts
+            from docutils.parsers.rst import directives
+            docutils_settings = dict([(alias, None) for alias in
+                                cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
+
+            docutils_settings.update({'input_encoding': 'unicode',
+                                      'report_level': 4})
+
+            for k, v in docutils_settings.iteritems():
+                directives.register_directive(k, v)
+
+            parts = publish_parts(source=source,
+                                  writer_name="html4css1",
+                                  settings_overrides=docutils_settings)
+
+            return parts['html_title'] + parts["fragment"]
+        except ImportError:
+            log.warning('Install docutils to use this function')
+            return cls.plain(source)
+
+    @classmethod
+    def rst_with_mentions(cls, source):
+        mention_pat = re.compile(r'(?:^@|\s@)(\w+)')
+
+        def wrapp(match_obj):
+            uname = match_obj.groups()[0]
+            return ' **@%(uname)s** ' % {'uname':uname}
+        mention_hl = mention_pat.sub(wrapp, source).strip()
+        return cls.rst(mention_hl)
--- a/rhodecode/lib/middleware/https_fixup.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/middleware/https_fixup.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: May 23, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
--- a/rhodecode/lib/middleware/simplegit.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/middleware/simplegit.py	Sun Feb 26 17:25:09 2012 +0200
@@ -8,7 +8,7 @@
 
     :created_on: Apr 28, 2010
     :author: marcink
-    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -27,7 +27,6 @@
 import os
 import logging
 import traceback
-import time
 
 from dulwich import server as dulserver
 
@@ -67,13 +66,12 @@
 from dulwich.repo import Repo
 from dulwich.web import HTTPGitApplication
 
-from paste.auth.basic import AuthBasicAuthenticator
 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 
 from rhodecode.lib import safe_str
-from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
-from rhodecode.lib.utils import invalidate_cache, is_valid_repo
-from rhodecode.model import meta
+from rhodecode.lib.base import BaseVCSController
+from rhodecode.lib.auth import get_container_username
+from rhodecode.lib.utils import is_valid_repo
 from rhodecode.model.db import User
 
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
@@ -93,24 +91,7 @@
     return False
 
 
-class SimpleGit(object):
-
-    def __init__(self, application, config):
-        self.application = application
-        self.config = config
-        # base path of repo locations
-        self.basepath = self.config['base_path']
-        #authenticate this mercurial request using authfunc
-        self.authenticate = AuthBasicAuthenticator('', authfunc)
-
-    def __call__(self, environ, start_response):
-        start = time.time()
-        try:
-            return self._handle_request(environ, start_response)
-        finally:
-            log = logging.getLogger(self.__class__.__name__)
-            log.debug('Request time: %.3fs' % (time.time() - start))
-            meta.Session.remove()
+class SimpleGit(BaseVCSController):
 
     def _handle_request(self, environ, start_response):
         if not is_git(environ):
@@ -143,9 +124,8 @@
         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)
+            anonymous_perm = self._check_permission(action, anonymous_user,
+                                                    repo_name)
 
             if anonymous_perm is not True or anonymous_user.active is False:
                 if anonymous_perm is not True:
@@ -159,27 +139,29 @@
                 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
                 #==============================================================
 
-                if not REMOTE_USER(environ):
+                # Attempting to retrieve username from the container
+                username = get_container_username(environ, self.config)
+
+                # If not authenticated by the container, running basic auth
+                if not username:
                     self.authenticate.realm = \
                         safe_str(self.config['rhodecode_realm'])
                     result = self.authenticate(environ)
                     if isinstance(result, str):
                         AUTH_TYPE.update(environ, 'basic')
                         REMOTE_USER.update(environ, result)
+                        username = result
                     else:
                         return result.wsgi_application(environ, start_response)
 
                 #==============================================================
-                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
-                # BASIC AUTH
+                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
                 #==============================================================
-
                 if action in ['pull', 'push']:
-                    username = REMOTE_USER(environ)
                     try:
                         user = self.__get_user(username)
-                        if user is None:
-                            return HTTPForbidden()(environ, start_response)                        
+                        if user is None or not user.active:
+                            return HTTPForbidden()(environ, start_response)
                         username = user.username
                     except:
                         log.error(traceback.format_exc())
@@ -187,16 +169,11 @@
                                                          start_response)
 
                     #check permissions for this repository
-                    perm = self.__check_permission(action, user,
+                    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}
-
         #===================================================================
         # GIT REQUEST HANDLING
         #===================================================================
@@ -211,8 +188,8 @@
         try:
             #invalidate cache on push
             if action == 'push':
-                self.__invalidate_cache(repo_name)
-
+                self._invalidate_cache(repo_name)
+            log.info('%s action on GIT repo "%s"' % (action, repo_name))
             app = self.__make_app(repo_name, repo_path)
             return app(environ, start_response)
         except Exception:
@@ -222,7 +199,7 @@
     def __make_app(self, repo_name, repo_path):
         """
         Make an wsgi application using dulserver
-        
+
         :param repo_name: name of the repository
         :param repo_path: full path to the repository
         """
@@ -233,31 +210,6 @@
 
         return gitserve
 
-    def __check_permission(self, action, user, repo_name):
-        """
-        Checks permissions using action (push/pull) user and repository
-        name
-
-        :param action: push or pull action
-        :param user: user instance
-        :param repo_name: repository name
-        """
-        if action == 'push':
-            if not HasPermissionAnyMiddleware('repository.write',
-                                              'repository.admin')(user,
-                                                                  repo_name):
-                return False
-
-        else:
-            #any other action need at least read permission
-            if not HasPermissionAnyMiddleware('repository.read',
-                                              'repository.write',
-                                              'repository.admin')(user,
-                                                                  repo_name):
-                return False
-
-        return True
-
     def __get_repository(self, environ):
         """
         Get's repository name out of PATH_INFO header
@@ -265,6 +217,7 @@
         :param environ: environ where PATH_INFO is stored
         """
         try:
+            environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
             repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
             if repo_name.endswith('/'):
                 repo_name = repo_name.rstrip('/')
@@ -293,10 +246,3 @@
                                service_cmd if service_cmd else 'other')
         else:
             return 'other'
-
-    def __invalidate_cache(self, repo_name):
-        """we know that some change was made to repositories and we should
-        invalidate the cache to see the changes right away but only for
-        push requests"""
-        invalidate_cache('get_repo_cached_%s' % repo_name)
-
--- a/rhodecode/lib/middleware/simplehg.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/middleware/simplehg.py	Sun Feb 26 17:25:09 2012 +0200
@@ -8,7 +8,7 @@
 
     :created_on: Apr 28, 2010
     :author: marcink
-    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -27,19 +27,16 @@
 import os
 import logging
 import traceback
-import time
 
 from mercurial.error import RepoError
 from mercurial.hgweb import hgweb_mod
 
-from paste.auth.basic import AuthBasicAuthenticator
 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 
 from rhodecode.lib import safe_str
-from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
-from rhodecode.lib.utils import make_ui, invalidate_cache, \
-    is_valid_repo, ui_sections
-from rhodecode.model import meta
+from rhodecode.lib.base import BaseVCSController
+from rhodecode.lib.auth import get_container_username
+from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
 from rhodecode.model.db import User
 
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
@@ -57,25 +54,7 @@
     return False
 
 
-class SimpleHg(object):
-
-    def __init__(self, application, config):
-        self.application = application
-        self.config = config
-        # base path of repo locations
-        self.basepath = self.config['base_path']
-        #authenticate this mercurial request using authfunc
-        self.authenticate = AuthBasicAuthenticator('', authfunc)
-        self.ipaddr = '0.0.0.0'
-
-    def __call__(self, environ, start_response):
-        start = time.time()
-        try:
-            return self._handle_request(environ, start_response)
-        finally:
-            log = logging.getLogger(self.__class__.__name__)
-            log.debug('Request time: %.3fs' % (time.time() - start))
-            meta.Session.remove()
+class SimpleHg(BaseVCSController):
 
     def _handle_request(self, environ, start_response):
         if not is_mercurial(environ):
@@ -101,7 +80,6 @@
         # GET ACTION PULL or PUSH
         #======================================================================
         action = self.__get_action(environ)
-
         #======================================================================
         # CHECK ANONYMOUS PERMISSION
         #======================================================================
@@ -109,9 +87,8 @@
             anonymous_user = self.__get_user('default')
 
             username = anonymous_user.username
-            anonymous_perm = self.__check_permission(action,
-                                                     anonymous_user,
-                                                     repo_name)
+            anonymous_perm = self._check_permission(action, anonymous_user,
+                                                    repo_name)
 
             if anonymous_perm is not True or anonymous_user.active is False:
                 if anonymous_perm is not True:
@@ -125,26 +102,28 @@
                 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
                 #==============================================================
 
-                if not REMOTE_USER(environ):
+                # Attempting to retrieve username from the container
+                username = get_container_username(environ, self.config)
+
+                # If not authenticated by the container, running basic auth
+                if not username:
                     self.authenticate.realm = \
                         safe_str(self.config['rhodecode_realm'])
                     result = self.authenticate(environ)
                     if isinstance(result, str):
                         AUTH_TYPE.update(environ, 'basic')
                         REMOTE_USER.update(environ, result)
+                        username = result
                     else:
                         return result.wsgi_application(environ, start_response)
 
                 #==============================================================
-                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
-                # BASIC AUTH
+                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
                 #==============================================================
-
                 if action in ['pull', 'push']:
-                    username = REMOTE_USER(environ)
                     try:
                         user = self.__get_user(username)
-                        if user is None:
+                        if user is None or not user.active:
                             return HTTPForbidden()(environ, start_response)
                         username = user.username
                     except:
@@ -153,7 +132,7 @@
                                                          start_response)
 
                     #check permissions for this repository
-                    perm = self.__check_permission(action, user,
+                    perm = self._check_permission(action, user,
                                                    repo_name)
                     if perm is not True:
                         return HTTPForbidden()(environ, start_response)
@@ -173,16 +152,15 @@
         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
+            # invalidate cache on push
             if action == 'push':
-                self.__invalidate_cache(repo_name)
-
+                self._invalidate_cache(repo_name)
+            log.info('%s action on HG repo "%s"' % (action, repo_name))
             app = self.__make_app(repo_path, baseui, extras)
             return app(environ, start_response)
         except RepoError, e:
@@ -199,32 +177,6 @@
         """
         return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
 
-
-    def __check_permission(self, action, user, repo_name):
-        """
-        Checks permissions using action (push/pull) user and repository
-        name
-
-        :param action: push or pull action
-        :param user: user instance
-        :param repo_name: repository name
-        """
-        if action == 'push':
-            if not HasPermissionAnyMiddleware('repository.write',
-                                              'repository.admin')(user,
-                                                                  repo_name):
-                return False
-
-        else:
-            #any other action need at least read permission
-            if not HasPermissionAnyMiddleware('repository.read',
-                                              'repository.write',
-                                              'repository.admin')(user,
-                                                                  repo_name):
-                return False
-
-        return True
-
     def __get_repository(self, environ):
         """
         Get's repository name out of PATH_INFO header
@@ -232,6 +184,7 @@
         :param environ: environ where PATH_INFO is stored
         """
         try:
+            environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
             repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
             if repo_name.endswith('/'):
                 repo_name = repo_name.rstrip('/')
@@ -265,18 +218,12 @@
                 else:
                     return 'pull'
 
-    def __invalidate_cache(self, repo_name):
-        """we know that some change was made to repositories and we should
-        invalidate the cache to see the changes right away but only for
-        push requests"""
-        invalidate_cache('get_repo_cached_%s' % repo_name)
-
     def __inject_extras(self, repo_path, baseui, extras={}):
         """
         Injects some extra params into baseui instance
-        
+
         also overwrites global settings with those takes from local hgrc file
-        
+
         :param baseui: baseui instance
         :param extras: dict with extra params to put into baseui
         """
@@ -298,4 +245,3 @@
             for section in ui_sections:
                 for k, v in repoui.configitems(section):
                     baseui.setconfig(section, k, v)
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/rcmail/exceptions.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,13 @@
+
+
+class InvalidMessage(RuntimeError):
+    """
+    Raised if message is missing vital headers, such
+    as recipients or sender address.
+    """
+
+
+class BadHeaders(RuntimeError):
+    """
+    Raised if message contains newlines in headers.
+    """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/rcmail/message.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,182 @@
+from rhodecode.lib.rcmail.response import MailResponse
+
+from rhodecode.lib.rcmail.exceptions import BadHeaders
+from rhodecode.lib.rcmail.exceptions import InvalidMessage
+
+class Attachment(object):
+    """
+    Encapsulates file attachment information.
+
+    :param filename: filename of attachment
+    :param content_type: file mimetype
+    :param data: the raw file data, either as string or file obj
+    :param disposition: content-disposition (if any)
+    """
+
+    def __init__(self,
+                 filename=None,
+                 content_type=None,
+                 data=None,
+                 disposition=None):
+
+        self.filename = filename
+        self.content_type = content_type
+        self.disposition = disposition or 'attachment'
+        self._data = data
+
+    @property
+    def data(self):
+        if isinstance(self._data, basestring):
+            return self._data
+        self._data = self._data.read()
+        return self._data
+
+
+class Message(object):
+    """
+    Encapsulates an email message.
+
+    :param subject: email subject header
+    :param recipients: list of email addresses
+    :param body: plain text message
+    :param html: HTML message
+    :param sender: email sender address
+    :param cc: CC list
+    :param bcc: BCC list
+    :param extra_headers: dict of extra email headers
+    :param attachments: list of Attachment instances
+    :param recipients_separator: alternative separator for any of
+        'From', 'To', 'Delivered-To', 'Cc', 'Bcc' fields
+    """
+
+    def __init__(self,
+                 subject=None,
+                 recipients=None,
+                 body=None,
+                 html=None,
+                 sender=None,
+                 cc=None,
+                 bcc=None,
+                 extra_headers=None,
+                 attachments=None,
+                 recipients_separator="; "):
+
+        self.subject = subject or ''
+        self.sender = sender
+        self.body = body
+        self.html = html
+
+        self.recipients = recipients or []
+        self.attachments = attachments or []
+        self.cc = cc or []
+        self.bcc = bcc or []
+        self.extra_headers = extra_headers or {}
+
+        self.recipients_separator = recipients_separator
+
+    @property
+    def send_to(self):
+        return set(self.recipients) | set(self.bcc or ()) | set(self.cc or ())
+
+    def to_message(self):
+        """
+        Returns raw email.Message instance.Validates message first.
+        """
+
+        self.validate()
+
+        return self.get_response().to_message()
+
+    def get_response(self):
+        """
+        Creates a Lamson MailResponse instance
+        """
+
+        response = MailResponse(Subject=self.subject,
+                                To=self.recipients,
+                                From=self.sender,
+                                Body=self.body,
+                                Html=self.html,
+                                separator=self.recipients_separator)
+
+        if self.cc:
+            response.base['Cc'] = self.cc
+
+        for attachment in self.attachments:
+
+            response.attach(attachment.filename,
+                            attachment.content_type,
+                            attachment.data,
+                            attachment.disposition)
+
+        response.update(self.extra_headers)
+
+        return response
+
+    def is_bad_headers(self):
+        """
+        Checks for bad headers i.e. newlines in subject, sender or recipients.
+        """
+
+        headers = [self.subject, self.sender]
+        headers += list(self.send_to)
+        headers += self.extra_headers.values()
+
+        for val in headers:
+            for c in '\r\n':
+                if c in val:
+                    return True
+        return False
+
+    def validate(self):
+        """
+        Checks if message is valid and raises appropriate exception.
+        """
+
+        if not self.recipients:
+            raise InvalidMessage, "No recipients have been added"
+
+        if not self.body and not self.html:
+            raise InvalidMessage, "No body has been set"
+
+        if not self.sender:
+            raise InvalidMessage, "No sender address has been set"
+
+        if self.is_bad_headers():
+            raise BadHeaders
+
+    def add_recipient(self, recipient):
+        """
+        Adds another recipient to the message.
+
+        :param recipient: email address of recipient.
+        """
+
+        self.recipients.append(recipient)
+
+    def add_cc(self, recipient):
+        """
+        Adds an email address to the CC list.
+
+        :param recipient: email address of recipient.
+        """
+
+        self.cc.append(recipient)
+
+    def add_bcc(self, recipient):
+        """
+        Adds an email address to the BCC list.
+
+        :param recipient: email address of recipient.
+        """
+
+        self.bcc.append(recipient)
+
+    def attach(self, attachment):
+        """
+        Adds an attachment to the message.
+
+        :param attachment: an **Attachment** instance.
+        """
+
+        self.attachments.append(attachment)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/rcmail/response.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,449 @@
+# The code in this module is entirely lifted from the Lamson project
+# (http://lamsonproject.org/).  Its copyright is:
+
+# Copyright (c) 2008, Zed A. Shaw
+# All rights reserved.
+
+# It is provided under this license:
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+
+# * Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer.
+
+# * Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+
+# * Neither the name of the Zed A. Shaw nor the names of its contributors may
+#   be used to endorse or promote products derived from this software without
+#   specific prior written permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import os
+import mimetypes
+import string
+from email import encoders
+from email.charset import Charset
+from email.utils import parseaddr
+from email.mime.base import MIMEBase
+
+ADDRESS_HEADERS_WHITELIST = ['From', 'To', 'Delivered-To', 'Cc']
+DEFAULT_ENCODING = "utf-8"
+VALUE_IS_EMAIL_ADDRESS = lambda v: '@' in v
+
+
+def normalize_header(header):
+    return string.capwords(header.lower(), '-')
+
+
+class EncodingError(Exception):
+    """Thrown when there is an encoding error."""
+    pass
+
+
+class MailBase(object):
+    """MailBase is used as the basis of lamson.mail and contains the basics of
+    encoding an email.  You actually can do all your email processing with this
+    class, but it's more raw.
+    """
+    def __init__(self, items=()):
+        self.headers = dict(items)
+        self.parts = []
+        self.body = None
+        self.content_encoding = {'Content-Type': (None, {}),
+                                 'Content-Disposition': (None, {}),
+                                 'Content-Transfer-Encoding': (None, {})}
+
+    def __getitem__(self, key):
+        return self.headers.get(normalize_header(key), None)
+
+    def __len__(self):
+        return len(self.headers)
+
+    def __iter__(self):
+        return iter(self.headers)
+
+    def __contains__(self, key):
+        return normalize_header(key) in self.headers
+
+    def __setitem__(self, key, value):
+        self.headers[normalize_header(key)] = value
+
+    def __delitem__(self, key):
+        del self.headers[normalize_header(key)]
+
+    def __nonzero__(self):
+        return self.body != None or len(self.headers) > 0 or len(self.parts) > 0
+
+    def keys(self):
+        """Returns the sorted keys."""
+        return sorted(self.headers.keys())
+
+    def attach_file(self, filename, data, ctype, disposition):
+        """
+        A file attachment is a raw attachment with a disposition that
+        indicates the file name.
+        """
+        assert filename, "You can't attach a file without a filename."
+        ctype = ctype.lower()
+
+        part = MailBase()
+        part.body = data
+        part.content_encoding['Content-Type'] = (ctype, {'name': filename})
+        part.content_encoding['Content-Disposition'] = (disposition,
+                                                        {'filename': filename})
+        self.parts.append(part)
+
+    def attach_text(self, data, ctype):
+        """
+        This attaches a simpler text encoded part, which doesn't have a
+        filename.
+        """
+        ctype = ctype.lower()
+
+        part = MailBase()
+        part.body = data
+        part.content_encoding['Content-Type'] = (ctype, {})
+        self.parts.append(part)
+
+    def walk(self):
+        for p in self.parts:
+            yield p
+            for x in p.walk():
+                yield x
+
+
+class MailResponse(object):
+    """
+    You are given MailResponse objects from the lamson.view methods, and
+    whenever you want to generate an email to send to someone.  It has the
+    same basic functionality as MailRequest, but it is designed to be written
+    to, rather than read from (although you can do both).
+
+    You can easily set a Body or Html during creation or after by passing it
+    as __init__ parameters, or by setting those attributes.
+
+    You can initially set the From, To, and Subject, but they are headers so
+    use the dict notation to change them: msg['From'] = 'joe@test.com'.
+
+    The message is not fully crafted until right when you convert it with
+    MailResponse.to_message.  This lets you change it and work with it, then
+    send it out when it's ready.
+    """
+    def __init__(self, To=None, From=None, Subject=None, Body=None, Html=None,
+                 separator="; "):
+        self.Body = Body
+        self.Html = Html
+        self.base = MailBase([('To', To), ('From', From), ('Subject', Subject)])
+        self.multipart = self.Body and self.Html
+        self.attachments = []
+        self.separator = separator
+
+    def __contains__(self, key):
+        return self.base.__contains__(key)
+
+    def __getitem__(self, key):
+        return self.base.__getitem__(key)
+
+    def __setitem__(self, key, val):
+        return self.base.__setitem__(key, val)
+
+    def __delitem__(self, name):
+        del self.base[name]
+
+    def attach(self, filename=None, content_type=None, data=None,
+               disposition=None):
+        """
+
+        Simplifies attaching files from disk or data as files.  To attach
+        simple text simple give data and a content_type.  To attach a file,
+        give the data/content_type/filename/disposition combination.
+
+        For convenience, if you don't give data and only a filename, then it
+        will read that file's contents when you call to_message() later.  If
+        you give data and filename then it will assume you've filled data
+        with what the file's contents are and filename is just the name to
+        use.
+        """
+
+        assert filename or data, ("You must give a filename or some data to "
+                                  "attach.")
+        assert data or os.path.exists(filename), ("File doesn't exist, and no "
+                                                  "data given.")
+
+        self.multipart = True
+
+        if filename and not content_type:
+            content_type, encoding = mimetypes.guess_type(filename)
+
+        assert content_type, ("No content type given, and couldn't guess "
+                              "from the filename: %r" % filename)
+
+        self.attachments.append({'filename': filename,
+                                 'content_type': content_type,
+                                 'data': data,
+                                 'disposition': disposition,})
+
+    def attach_part(self, part):
+        """
+        Attaches a raw MailBase part from a MailRequest (or anywhere)
+        so that you can copy it over.
+        """
+        self.multipart = True
+
+        self.attachments.append({'filename': None,
+                                 'content_type': None,
+                                 'data': None,
+                                 'disposition': None,
+                                 'part': part,
+                                 })
+
+    def attach_all_parts(self, mail_request):
+        """
+        Used for copying the attachment parts of a mail.MailRequest
+        object for mailing lists that need to maintain attachments.
+        """
+        for part in mail_request.all_parts():
+            self.attach_part(part)
+
+        self.base.content_encoding = mail_request.base.content_encoding.copy()
+
+    def clear(self):
+        """
+        Clears out the attachments so you can redo them.  Use this to keep the
+        headers for a series of different messages with different attachments.
+        """
+        del self.attachments[:]
+        del self.base.parts[:]
+        self.multipart = False
+
+    def update(self, message):
+        """
+        Used to easily set a bunch of heading from another dict
+        like object.
+        """
+        for k in message.keys():
+            self.base[k] = message[k]
+
+    def __str__(self):
+        """
+        Converts to a string.
+        """
+        return self.to_message().as_string()
+
+    def _encode_attachment(self, filename=None, content_type=None, data=None,
+                           disposition=None, part=None):
+        """
+        Used internally to take the attachments mentioned in self.attachments
+        and do the actual encoding in a lazy way when you call to_message.
+        """
+        if part:
+            self.base.parts.append(part)
+        elif filename:
+            if not data:
+                data = open(filename).read()
+
+            self.base.attach_file(filename, data, content_type,
+                                  disposition or 'attachment')
+        else:
+            self.base.attach_text(data, content_type)
+
+        ctype = self.base.content_encoding['Content-Type'][0]
+
+        if ctype and not ctype.startswith('multipart'):
+            self.base.content_encoding['Content-Type'] = ('multipart/mixed', {})
+
+    def to_message(self):
+        """
+        Figures out all the required steps to finally craft the
+        message you need and return it.  The resulting message
+        is also available as a self.base attribute.
+
+        What is returned is a Python email API message you can
+        use with those APIs.  The self.base attribute is the raw
+        lamson.encoding.MailBase.
+        """
+        del self.base.parts[:]
+
+        if self.Body and self.Html:
+            self.multipart = True
+            self.base.content_encoding['Content-Type'] = (
+                'multipart/alternative', {})
+
+        if self.multipart:
+            self.base.body = None
+            if self.Body:
+                self.base.attach_text(self.Body, 'text/plain')
+
+            if self.Html:
+                self.base.attach_text(self.Html, 'text/html')
+
+            for args in self.attachments:
+                self._encode_attachment(**args)
+
+        elif self.Body:
+            self.base.body = self.Body
+            self.base.content_encoding['Content-Type'] = ('text/plain', {})
+
+        elif self.Html:
+            self.base.body = self.Html
+            self.base.content_encoding['Content-Type'] = ('text/html', {})
+
+        return to_message(self.base, separator=self.separator)
+
+    def all_parts(self):
+        """
+        Returns all the encoded parts.  Only useful for debugging
+        or inspecting after calling to_message().
+        """
+        return self.base.parts
+
+    def keys(self):
+        return self.base.keys()
+
+
+def to_message(mail, separator="; "):
+    """
+    Given a MailBase message, this will construct a MIMEPart
+    that is canonicalized for use with the Python email API.
+    """
+    ctype, params = mail.content_encoding['Content-Type']
+
+    if not ctype:
+        if mail.parts:
+            ctype = 'multipart/mixed'
+        else:
+            ctype = 'text/plain'
+    else:
+        if mail.parts:
+            assert ctype.startswith(("multipart", "message")), \
+                   "Content type should be multipart or message, not %r" % ctype
+
+    # adjust the content type according to what it should be now
+    mail.content_encoding['Content-Type'] = (ctype, params)
+
+    try:
+        out = MIMEPart(ctype, **params)
+    except TypeError, exc:  # pragma: no cover
+        raise EncodingError("Content-Type malformed, not allowed: %r; "
+                            "%r (Python ERROR: %s" %
+                            (ctype, params, exc.message))
+
+    for k in mail.keys():
+        if k in ADDRESS_HEADERS_WHITELIST:
+            out[k.encode('ascii')] = header_to_mime_encoding(
+                                         mail[k],
+                                         not_email=False,
+                                         separator=separator
+                                     )
+        else:
+            out[k.encode('ascii')] = header_to_mime_encoding(
+                                         mail[k],
+                                         not_email=True
+                                    )
+
+    out.extract_payload(mail)
+
+    # go through the children
+    for part in mail.parts:
+        out.attach(to_message(part))
+
+    return out
+
+class MIMEPart(MIMEBase):
+    """
+    A reimplementation of nearly everything in email.mime to be more useful
+    for actually attaching things.  Rather than one class for every type of
+    thing you'd encode, there's just this one, and it figures out how to
+    encode what you ask it.
+    """
+    def __init__(self, type, **params):
+        self.maintype, self.subtype = type.split('/')
+        MIMEBase.__init__(self, self.maintype, self.subtype, **params)
+
+    def add_text(self, content):
+        # this is text, so encode it in canonical form
+        try:
+            encoded = content.encode('ascii')
+            charset = 'ascii'
+        except UnicodeError:
+            encoded = content.encode('utf-8')
+            charset = 'utf-8'
+
+        self.set_payload(encoded, charset=charset)
+
+    def extract_payload(self, mail):
+        if mail.body == None: return  # only None, '' is still ok
+
+        ctype, ctype_params = mail.content_encoding['Content-Type']
+        cdisp, cdisp_params = mail.content_encoding['Content-Disposition']
+
+        assert ctype, ("Extract payload requires that mail.content_encoding "
+                       "have a valid Content-Type.")
+
+        if ctype.startswith("text/"):
+            self.add_text(mail.body)
+        else:
+            if cdisp:
+                # replicate the content-disposition settings
+                self.add_header('Content-Disposition', cdisp, **cdisp_params)
+
+            self.set_payload(mail.body)
+            encoders.encode_base64(self)
+
+    def __repr__(self):
+        return "<MIMEPart '%s/%s': %r, %r, multipart=%r>" % (
+            self.subtype,
+            self.maintype,
+            self['Content-Type'],
+            self['Content-Disposition'],
+            self.is_multipart())
+
+
+def header_to_mime_encoding(value, not_email=False, separator=", "):
+    if not value: return ""
+
+    encoder = Charset(DEFAULT_ENCODING)
+    if type(value) == list:
+        return separator.join(properly_encode_header(
+            v, encoder, not_email) for v in value)
+    else:
+        return properly_encode_header(value, encoder, not_email)
+
+def properly_encode_header(value, encoder, not_email):
+    """
+    The only thing special (weird) about this function is that it tries
+    to do a fast check to see if the header value has an email address in
+    it.  Since random headers could have an email address, and email addresses
+    have weird special formatting rules, we have to check for it.
+
+    Normally this works fine, but in Librelist, we need to "obfuscate" email
+    addresses by changing the '@' to '-AT-'.  This is where
+    VALUE_IS_EMAIL_ADDRESS exists.  It's a simple lambda returning True/False
+    to check if a header value has an email address.  If you need to make this
+    check different, then change this.
+    """
+    try:
+        return value.encode("ascii")
+    except UnicodeEncodeError:
+        if not_email is False and VALUE_IS_EMAIL_ADDRESS(value):
+            # this could have an email address, make sure we don't screw it up
+            name, address = parseaddr(value)
+            return '"%s" <%s>' % (
+                encoder.header_encode(name.encode("utf-8")), address)
+
+        return encoder.header_encode(value.encode("utf-8"))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/rcmail/smtp_mailer.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.lib.rcmail.smtp_mailer
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Simple smtp mailer used in RhodeCode
+
+    :created_on: Sep 13, 2010
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import smtplib
+from socket import sslerror
+from rhodecode.lib.rcmail.message import Message
+
+
+class SmtpMailer(object):
+    """SMTP mailer class
+
+    mailer = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth
+                        mail_port, ssl, tls)
+    mailer.send(recipients, subject, body, attachment_files)
+
+    :param recipients might be a list of string or single string
+    :param attachment_files is a dict of {filename:location}
+        it tries to guess the mimetype and attach the file
+
+    """
+
+    def __init__(self, mail_from, user, passwd, mail_server, smtp_auth=None,
+                 mail_port=None, ssl=False, tls=False, debug=False):
+
+        self.mail_from = mail_from
+        self.mail_server = mail_server
+        self.mail_port = mail_port
+        self.user = user
+        self.passwd = passwd
+        self.ssl = ssl
+        self.tls = tls
+        self.debug = debug
+        self.auth = smtp_auth
+
+    def send(self, recipients=[], subject='', body='', html='',
+             attachment_files=None):
+
+        if isinstance(recipients, basestring):
+            recipients = [recipients]
+        msg = Message(subject, recipients, body, html, self.mail_from,
+                      recipients_separator=", ")
+        raw_msg = msg.to_message()
+
+        if self.ssl:
+            smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
+        else:
+            smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
+
+        if self.tls:
+            smtp_serv.ehlo()
+            smtp_serv.starttls()
+
+        if self.debug:
+            smtp_serv.set_debuglevel(1)
+
+        smtp_serv.ehlo()
+        if self.auth:
+            smtp_serv.esmtp_features["auth"] = self.auth
+
+        # if server requires authorization you must provide login and password
+        # but only if we have them
+        if self.user and self.passwd:
+            smtp_serv.login(self.user, self.passwd)
+
+        smtp_serv.sendmail(msg.sender, msg.send_to, raw_msg.as_string())
+        logging.info('MAIL SEND TO: %s' % recipients)
+
+        try:
+            smtp_serv.quit()
+        except sslerror:
+            # sslerror is raised in tls connections on closing sometimes
+            pass
--- a/rhodecode/lib/smtp_mailer.py	Sun Feb 19 20:21:14 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,165 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    rhodecode.lib.smtp_mailer
-    ~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Simple smtp mailer used in RhodeCode
-
-    :created_on: Sep 13, 2010
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
-    :license: GPLv3, see COPYING for more details.
-"""
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-import logging
-import smtplib
-import mimetypes
-from socket import sslerror
-
-from email.mime.multipart import MIMEMultipart
-from email.mime.image import MIMEImage
-from email.mime.audio import MIMEAudio
-from email.mime.base import MIMEBase
-from email.mime.text import MIMEText
-from email.utils import formatdate
-from email import encoders
-
-
-class SmtpMailer(object):
-    """SMTP mailer class
-
-    mailer = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth
-                        mail_port, ssl, tls)
-    mailer.send(recipients, subject, body, attachment_files)
-
-    :param recipients might be a list of string or single string
-    :param attachment_files is a dict of {filename:location}
-        it tries to guess the mimetype and attach the file
-
-    """
-
-    def __init__(self, mail_from, user, passwd, mail_server, smtp_auth=None,
-                 mail_port=None, ssl=False, tls=False, debug=False):
-
-        self.mail_from = mail_from
-        self.mail_server = mail_server
-        self.mail_port = mail_port
-        self.user = user
-        self.passwd = passwd
-        self.ssl = ssl
-        self.tls = tls
-        self.debug = debug
-        self.auth = smtp_auth
-
-    def send(self, recipients=[], subject='', body='', attachment_files=None):
-
-        if isinstance(recipients, basestring):
-            recipients = [recipients]
-        if self.ssl:
-            smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
-        else:
-            smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
-
-        if self.tls:
-            smtp_serv.ehlo()
-            smtp_serv.starttls()
-
-        if self.debug:
-            smtp_serv.set_debuglevel(1)
-
-        smtp_serv.ehlo()
-        if self.auth:
-            smtp_serv.esmtp_features["auth"] = self.auth
-
-        # if server requires authorization you must provide login and password
-        # but only if we have them
-        if self.user and self.passwd:
-            smtp_serv.login(self.user, self.passwd)
-
-        date_ = formatdate(localtime=True)
-        msg = MIMEMultipart()
-        msg.set_type('multipart/alternative')
-        msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
-
-        text_msg = MIMEText(body)
-        text_msg.set_type('text/plain')
-        text_msg.set_param('charset', 'UTF-8')
-
-        msg['From'] = self.mail_from
-        msg['To'] = ','.join(recipients)
-        msg['Date'] = date_
-        msg['Subject'] = subject
-
-        msg.attach(text_msg)
-
-        if attachment_files:
-            self.__atach_files(msg, attachment_files)
-
-        smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
-        logging.info('MAIL SEND TO: %s' % recipients)
-
-        try:
-            smtp_serv.quit()
-        except sslerror:
-            # sslerror is raised in tls connections on closing sometimes
-            pass
-
-    def __atach_files(self, msg, attachment_files):
-        if isinstance(attachment_files, dict):
-            for f_name, msg_file in attachment_files.items():
-                ctype, encoding = mimetypes.guess_type(f_name)
-                logging.info("guessing file %s type based on %s", ctype,
-                             f_name)
-                if ctype is None or encoding is not None:
-                    # No guess could be made, or the file is encoded
-                    # (compressed), so use a generic bag-of-bits type.
-                    ctype = 'application/octet-stream'
-                maintype, subtype = ctype.split('/', 1)
-                if maintype == 'text':
-                    # Note: we should handle calculating the charset
-                    file_part = MIMEText(self.get_content(msg_file),
-                                         _subtype=subtype)
-                elif maintype == 'image':
-                    file_part = MIMEImage(self.get_content(msg_file),
-                                          _subtype=subtype)
-                elif maintype == 'audio':
-                    file_part = MIMEAudio(self.get_content(msg_file),
-                                          _subtype=subtype)
-                else:
-                    file_part = MIMEBase(maintype, subtype)
-                    file_part.set_payload(self.get_content(msg_file))
-                    # Encode the payload using Base64
-                    encoders.encode_base64(msg)
-                # Set the filename parameter
-                file_part.add_header('Content-Disposition', 'attachment',
-                                     filename=f_name)
-                file_part.add_header('Content-Type', ctype, name=f_name)
-                msg.attach(file_part)
-        else:
-            raise Exception('Attachment files should be'
-                            'a dict in format {"filename":"filepath"}')
-
-    def get_content(self, msg_file):
-        """Get content based on type, if content is a string do open first
-        else just read because it's a probably open file object
-
-        :param msg_file:
-        """
-        if isinstance(msg_file, str):
-            return open(msg_file, "rb").read()
-        else:
-            # just for safe seek to 0
-            msg_file.seek(0)
-            return msg_file.read()
-
--- a/rhodecode/lib/utils.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/lib/utils.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 18, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -29,6 +29,9 @@
 import traceback
 import paste
 import beaker
+import tarfile
+import shutil
+from os.path import abspath
 from os.path import dirname as dn, join as jn
 
 from paste.script.command import Command, BadCommand
@@ -37,25 +40,27 @@
 
 from webhelpers.text import collapse, remove_formatting, strip_tags
 
-from vcs import get_backend
-from vcs.backends.base import BaseChangeset
-from vcs.utils.lazy import LazyProperty
-from vcs.utils.helpers import get_scm
-from vcs.exceptions import VCSError
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.backends.base import BaseChangeset
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.exceptions import VCSError
+
+from rhodecode.lib.caching_query import FromCache
 
 from rhodecode.model import meta
-from rhodecode.model.caching_query import FromCache
-from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group, \
-    RhodeCodeSettings
-from rhodecode.model.repo import RepoModel
+from rhodecode.model.db import Repository, User, RhodeCodeUi, \
+    UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
+from rhodecode.model.meta import Session
+from rhodecode.model.repos_group import ReposGroupModel
 
 log = logging.getLogger(__name__)
 
 
-def recursive_replace(str, replace=' '):
+def recursive_replace(str_, replace=' '):
     """Recursive replace of given sign to just one instance
 
-    :param str: given string
+    :param str_: given string
     :param replace: char to find and replace multiple instances
 
     Examples::
@@ -63,11 +68,11 @@
     'Mighty-Mighty-Bo-sstones'
     """
 
-    if str.find(replace * 2) == -1:
-        return str
+    if str_.find(replace * 2) == -1:
+        return str_
     else:
-        str = str.replace(replace * 2, replace)
-        return recursive_replace(str, replace)
+        str_ = str_.replace(replace * 2, replace)
+        return recursive_replace(str_, replace)
 
 
 def repo_name_slug(value):
@@ -90,7 +95,11 @@
     return request.environ['pylons.routes_dict'].get('repo_name')
 
 
-def action_logger(user, action, repo, ipaddr='', sa=None):
+def get_repos_group_slug(request):
+    return request.environ['pylons.routes_dict'].get('group_name')
+
+
+def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
     """
     Action logger for various actions made by users
 
@@ -106,7 +115,7 @@
     """
 
     if not sa:
-        sa = meta.Session()
+        sa = meta.Session
 
     try:
         if hasattr(user, 'user_id'):
@@ -116,13 +125,12 @@
         else:
             raise Exception('You have to provide user object or username')
 
-        rm = RepoModel()
         if hasattr(repo, 'repo_id'):
-            repo_obj = rm.get(repo.repo_id, cache=False)
+            repo_obj = Repository.get(repo.repo_id)
             repo_name = repo_obj.repo_name
         elif  isinstance(repo, basestring):
             repo_name = repo.lstrip('/')
-            repo_obj = rm.get_by_repo_name(repo_name, cache=False)
+            repo_obj = Repository.get_by_repo_name(repo_name)
         else:
             raise Exception('You have to provide repository to action logger')
 
@@ -136,26 +144,25 @@
         user_log.action_date = datetime.datetime.now()
         user_log.user_ip = ipaddr
         sa.add(user_log)
-        sa.commit()
 
-        log.info('Adding user %s, action %s on %s', user_obj, action, repo)
+        log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
+        if commit:
+            sa.commit()
     except:
         log.error(traceback.format_exc())
-        sa.rollback()
+        raise
 
 
 def get_repos(path, recursive=False):
     """
     Scans given path for repos and return (name,(type,path)) tuple
 
-    :param path: path to scann for repositories
+    :param path: path to scan for repositories
     :param recursive: recursive search and return names with subdirs in front
     """
-    from vcs.utils.helpers import get_scm
-    from vcs.exceptions import VCSError
 
     # remove ending slash for better results
-    path = path.rstrip('/')
+    path = path.rstrip(os.sep)
 
     def _get_repos(p):
         if not os.access(p, os.W_OK):
@@ -195,10 +202,11 @@
     except VCSError:
         return False
 
+
 def is_valid_repos_group(repos_group_name, base_path):
     """
     Returns True if given path is a repos group False otherwise
-    
+
     :param repo_name:
     :param base_path:
     """
@@ -214,6 +222,7 @@
 
     return False
 
+
 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
     while True:
         ok = raw_input(prompt)
@@ -250,28 +259,28 @@
 
     baseui = ui.ui()
 
-    #clean the baseui object
+    # clean the baseui object
     baseui._ocfg = config.config()
     baseui._ucfg = config.config()
     baseui._tcfg = config.config()
 
     if read_from == 'file':
         if not os.path.isfile(path):
-            log.warning('Unable to read config file %s' % path)
+            log.debug('hgrc file is not present at %s skipping...' % path)
             return False
-        log.debug('reading hgrc from %s', path)
+        log.debug('reading hgrc from %s' % path)
         cfg = config.config()
         cfg.read(path)
         for section in ui_sections:
             for k, v in cfg.items(section):
-                log.debug('settings ui from file[%s]%s:%s', section, k, v)
+                log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
                 baseui.setconfig(section, k, v)
 
     elif read_from == 'db':
-        sa = meta.Session()
+        sa = meta.Session
         ret = sa.query(RhodeCodeUi)\
-            .options(FromCache("sql_cache_short",
-                               "get_hg_ui_settings")).all()
+            .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
+            .all()
 
         hg_ui = ret
         for ui_ in hg_ui:
@@ -285,18 +294,20 @@
 
 
 def set_rhodecode_config(config):
-    """Updates pylons config with new settings from database
+    """
+    Updates pylons config with new settings from database
 
     :param config:
     """
-    hgsettings = RhodeCodeSettings.get_app_settings()
+    hgsettings = RhodeCodeSetting.get_app_settings()
 
     for k, v in hgsettings.items():
         config[k] = v
 
 
 def invalidate_cache(cache_key, *args):
-    """Puts cache invalidation task into db for
+    """
+    Puts cache invalidation task into db for
     further global cache invalidation
     """
 
@@ -313,7 +324,8 @@
     an EmptyChangeset
     """
 
-    def __init__(self, cs='0' * 40, repo=None, requested_revision=None, alias=None):
+    def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
+                 alias=None):
         self._empty_cs = cs
         self.revision = -1
         self.message = ''
@@ -325,7 +337,8 @@
 
     @LazyProperty
     def raw_id(self):
-        """Returns raw string identifying this changeset, useful for web
+        """
+        Returns raw string identifying this changeset, useful for web
         representation.
         """
 
@@ -350,66 +363,74 @@
 
 
 def map_groups(groups):
-    """Checks for groups existence, and creates groups structures.
+    """
+    Checks for groups existence, and creates groups structures.
     It returns last group in structure
 
     :param groups: list of groups structure
     """
-    sa = meta.Session()
+    sa = meta.Session
 
     parent = None
     group = None
 
     # last element is repo in nested groups structure
     groups = groups[:-1]
-
+    rgm = ReposGroupModel(sa)
     for lvl, group_name in enumerate(groups):
         group_name = '/'.join(groups[:lvl] + [group_name])
-        group = sa.query(Group).filter(Group.group_name == group_name).scalar()
+        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()
 
         if group is None:
-            group = Group(group_name, parent)
+            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()
         parent = group
     return group
 
 
 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
-    """maps all repos given in initial_repo_list, non existing repositories
+    """
+    maps all repos given in initial_repo_list, non existing repositories
     are created, if remove_obsolete is True it also check for db entries
     that are not in initial_repo_list and removes them.
 
     :param initial_repo_list: list of repositories found by scanning methods
     :param remove_obsolete: check for obsolete entries in database
     """
-
-    sa = meta.Session()
+    from rhodecode.model.repo import RepoModel
+    sa = meta.Session
     rm = RepoModel()
     user = sa.query(User).filter(User.admin == True).first()
+    if user is None:
+        raise Exception('Missing administrative account !')
     added = []
-    # fixup groups paths to new format on the fly
-    # TODO: remove this in future
-    for g in Group.query().all():
-        g.group_name = g.get_new_name(g.name)
-        sa.add(g)    
+
     for name, repo in initial_repo_list.items():
         group = map_groups(name.split(Repository.url_sep()))
         if not rm.get_by_repo_name(name, cache=False):
-            log.info('repository %s not found creating default', name)
+            log.info('repository %s not found creating default' % name)
             added.append(name)
             form_data = {
-                         'repo_name': name,
-                         'repo_name_full': name,
-                         'repo_type': repo.alias,
-                         'description': repo.description \
-                            if repo.description != 'unknown' else \
-                                        '%s repository' % name,
-                         'private': False,
-                         'group_id': getattr(group, 'group_id', None)
-                         }
+             'repo_name': name,
+             'repo_name_full': name,
+             'repo_type': repo.alias,
+             'description': repo.description \
+                if repo.description != 'unknown' else '%s repository' % name,
+             'private': False,
+             'group_id': getattr(group, 'group_id', None)
+            }
             rm.create(form_data, user, just_db=True)
-
+    sa.commit()
     removed = []
     if remove_obsolete:
         #remove from database those repositories that are not in the filesystem
@@ -421,7 +442,8 @@
 
     return added, removed
 
-#set cache regions for beaker so celery can utilise it
+
+# set cache regions for beaker so celery can utilise it
 def add_cache(settings):
     cache_settings = {'regions': None}
     for key in settings.keys():
@@ -455,7 +477,7 @@
 def create_test_index(repo_location, config, full_index):
     """
     Makes default test index
-    
+
     :param config: test config
     :param full_index:
     """
@@ -480,19 +502,16 @@
 
 
 def create_test_env(repos_test_path, config):
-    """Makes a fresh database and
+    """
+    Makes a fresh database and
     install test repository into tmp dir
     """
     from rhodecode.lib.db_manage import DbManage
-    from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
-        HG_FORK, GIT_FORK, TESTS_TMP_PATH
-    import tarfile
-    import shutil
-    from os.path import abspath
+    from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
 
     # PART ONE create db
     dbconf = config['sqlalchemy.db1.url']
-    log.debug('making test db %s', dbconf)
+    log.debug('making test db %s' % dbconf)
 
     # create test dir if it doesn't exist
     if not os.path.isdir(repos_test_path):
@@ -507,7 +526,7 @@
     dbmanage.admin_prompt()
     dbmanage.create_permissions()
     dbmanage.populate_default_permissions()
-
+    Session.commit()
     # PART TWO make test repo
     log.debug('making test vcs repositories')
 
@@ -595,4 +614,3 @@
         path_to_ini_file = os.path.realpath(conf)
         conf = paste.deploy.appconfig('config:' + 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/vcs/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+"""
+    vcs
+    ~~~
+
+    Various version Control System (vcs) management abstraction layer for
+    Python.
+
+    :created_on: Apr 8, 2010
+    :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
+"""
+
+VERSION = (0, 2, 3, 'dev')
+
+__version__ = '.'.join((str(each) for each in VERSION[:4]))
+
+__all__ = [
+    'get_version', 'get_repo', 'get_backend',
+    'VCSError', 'RepositoryError', 'ChangesetError']
+
+import sys
+from rhodecode.lib.vcs.backends import get_repo, get_backend
+from rhodecode.lib.vcs.exceptions import VCSError, RepositoryError, ChangesetError
+
+
+def get_version():
+    """
+    Returns shorter version (digit parts only) as string.
+    """
+    return '.'.join((str(each) for each in VERSION[:3]))
+
+def main(argv=None):
+    if argv is None:
+        argv = sys.argv
+    from rhodecode.lib.vcs.cli import ExecutionManager
+    manager = ExecutionManager(argv)
+    manager.execute()
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+"""
+    vcs.backends
+    ~~~~~~~~~~~~
+
+    Main package for scm backends
+
+    :created_on: Apr 8, 2010
+    :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
+"""
+import os
+from pprint import pformat
+from rhodecode.lib.vcs.conf import settings
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.utils.paths import abspath
+from rhodecode.lib.vcs.utils.imports import import_class
+
+
+def get_repo(path=None, alias=None, create=False):
+    """
+    Returns ``Repository`` object of type linked with given ``alias`` at
+    the specified ``path``. If ``alias`` is not given it will try to guess it
+    using get_scm method
+    """
+    if create:
+        if not (path or alias):
+            raise TypeError("If create is specified, we need path and scm type")
+        return get_backend(alias)(path, create=True)
+    if path is None:
+        path = abspath(os.path.curdir)
+    try:
+        scm, path = get_scm(path, search_recursively=True)
+        path = abspath(path)
+        alias = scm
+    except VCSError:
+        raise VCSError("No scm found at %s" % path)
+    if alias is None:
+        alias = get_scm(path)[0]
+
+    backend = get_backend(alias)
+    repo = backend(path, create=create)
+    return repo
+
+
+def get_backend(alias):
+    """
+    Returns ``Repository`` class identified by the given alias or raises
+    VCSError if alias is not recognized or backend class cannot be imported.
+    """
+    if alias not in settings.BACKENDS:
+        raise VCSError("Given alias '%s' is not recognized! Allowed aliases:\n"
+            "%s" % (alias, pformat(settings.BACKENDS.keys())))
+    backend_path = settings.BACKENDS[alias]
+    klass = import_class(backend_path)
+    return klass
+
+
+def get_supported_backends():
+    """
+    Returns list of aliases of supported backends.
+    """
+    return settings.BACKENDS.keys()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/base.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,911 @@
+# -*- coding: utf-8 -*-
+"""
+    vcs.backends.base
+    ~~~~~~~~~~~~~~~~~
+
+    Base for all available scm backends
+
+    :created_on: Apr 8, 2010
+    :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
+"""
+
+
+from itertools import chain
+from rhodecode.lib.vcs.utils import author_name, author_email
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs
+from rhodecode.lib.vcs.conf import settings
+
+from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
+    NodeAlreadyAddedError, NodeAlreadyChangedError, NodeAlreadyExistsError, \
+    NodeAlreadyRemovedError, NodeDoesNotExistError, NodeNotChangedError, \
+    RepositoryError
+
+
+class BaseRepository(object):
+    """
+    Base Repository for final backends
+
+    **Attributes**
+
+        ``DEFAULT_BRANCH_NAME``
+            name of default branch (i.e. "trunk" for svn, "master" for git etc.
+
+        ``scm``
+            alias of scm, i.e. *git* or *hg*
+
+        ``repo``
+            object from external api
+
+        ``revisions``
+            list of all available revisions' ids, in ascending order
+
+        ``changesets``
+            storage dict caching returned changesets
+
+        ``path``
+            absolute path to the repository
+
+        ``branches``
+            branches as list of changesets
+
+        ``tags``
+            tags as list of changesets
+    """
+    scm = None
+    DEFAULT_BRANCH_NAME = None
+    EMPTY_CHANGESET = '0' * 40
+
+    def __init__(self, repo_path, create=False, **kwargs):
+        """
+        Initializes repository. Raises RepositoryError if repository could
+        not be find at the given ``repo_path`` or directory at ``repo_path``
+        exists and ``create`` is set to True.
+
+        :param repo_path: local path of the repository
+        :param create=False: if set to True, would try to craete repository.
+        :param src_url=None: if set, should be proper url from which repository
+          would be cloned; requires ``create`` parameter to be set to True -
+          raises RepositoryError if src_url is set and create evaluates to
+          False
+        """
+        raise NotImplementedError
+
+    def __str__(self):
+        return '<%s at %s>' % (self.__class__.__name__, self.path)
+
+    def __repr__(self):
+        return self.__str__()
+
+    def __len__(self):
+        return self.count()
+
+    @LazyProperty
+    def alias(self):
+        for k, v in settings.BACKENDS.items():
+            if v.split('.')[-1] == str(self.__class__.__name__):
+                return k
+
+    @LazyProperty
+    def name(self):
+        raise NotImplementedError
+
+    @LazyProperty
+    def owner(self):
+        raise NotImplementedError
+
+    @LazyProperty
+    def description(self):
+        raise NotImplementedError
+
+    @LazyProperty
+    def size(self):
+        """
+        Returns combined size in bytes for all repository files
+        """
+
+        size = 0
+        try:
+            tip = self.get_changeset()
+            for topnode, dirs, files in tip.walk('/'):
+                for f in files:
+                    size += tip.get_file_size(f.path)
+                for dir in dirs:
+                    for f in files:
+                        size += tip.get_file_size(f.path)
+
+        except RepositoryError, e:
+            pass
+        return size
+
+    def is_valid(self):
+        """
+        Validates repository.
+        """
+        raise NotImplementedError
+
+    def get_last_change(self):
+        self.get_changesets()
+
+    #==========================================================================
+    # CHANGESETS
+    #==========================================================================
+
+    def get_changeset(self, revision=None):
+        """
+        Returns instance of ``Changeset`` class. If ``revision`` is None, most
+        recent changeset is returned.
+
+        :raises ``EmptyRepositoryError``: if there are no revisions
+        """
+        raise NotImplementedError
+
+    def __iter__(self):
+        """
+        Allows Repository objects to be iterated.
+
+        *Requires* implementation of ``__getitem__`` method.
+        """
+        for revision in self.revisions:
+            yield self.get_changeset(revision)
+
+    def get_changesets(self, start=None, end=None, start_date=None,
+                       end_date=None, branch_name=None, reverse=False):
+        """
+        Returns iterator of ``MercurialChangeset`` objects from start to end
+        not inclusive This should behave just like a list, ie. end is not
+        inclusive
+
+        :param start: None or str
+        :param end: None or str
+        :param start_date:
+        :param end_date:
+        :param branch_name:
+        :param reversed:
+        """
+        raise NotImplementedError
+
+    def __getslice__(self, i, j):
+        """
+        Returns a iterator of sliced repository
+        """
+        for rev in self.revisions[i:j]:
+            yield self.get_changeset(rev)
+
+    def __getitem__(self, key):
+        return self.get_changeset(key)
+
+    def count(self):
+        return len(self.revisions)
+
+    def tag(self, name, user, revision=None, message=None, date=None, **opts):
+        """
+        Creates and returns a tag for the given ``revision``.
+
+        :param name: name for new tag
+        :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
+        :param revision: changeset id for which new tag would be created
+        :param message: message of the tag's commit
+        :param date: date of tag's commit
+
+        :raises TagAlreadyExistError: if tag with same name already exists
+        """
+        raise NotImplementedError
+
+    def remove_tag(self, name, user, message=None, date=None):
+        """
+        Removes tag with the given ``name``.
+
+        :param name: name of the tag to be removed
+        :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
+        :param message: message of the tag's removal commit
+        :param date: date of tag's removal commit
+
+        :raises TagDoesNotExistError: if tag with given name does not exists
+        """
+        raise NotImplementedError
+
+    def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
+            context=3):
+        """
+        Returns (git like) *diff*, as plain text. Shows changes introduced by
+        ``rev2`` since ``rev1``.
+
+        :param rev1: Entry point from which diff is shown. Can be
+          ``self.EMPTY_CHANGESET`` - in this case, patch showing all
+          the changes since empty state of the repository until ``rev2``
+        :param rev2: Until which revision changes should be shown.
+        :param ignore_whitespace: If set to ``True``, would not show whitespace
+          changes. Defaults to ``False``.
+        :param context: How many lines before/after changed lines should be
+          shown. Defaults to ``3``.
+        """
+        raise NotImplementedError
+
+    # ========== #
+    # COMMIT API #
+    # ========== #
+
+    @LazyProperty
+    def in_memory_changeset(self):
+        """
+        Returns ``InMemoryChangeset`` object for this repository.
+        """
+        raise NotImplementedError
+
+    def add(self, filenode, **kwargs):
+        """
+        Commit api function that will add given ``FileNode`` into this
+        repository.
+
+        :raises ``NodeAlreadyExistsError``: if there is a file with same path
+          already in repository
+        :raises ``NodeAlreadyAddedError``: if given node is already marked as
+          *added*
+        """
+        raise NotImplementedError
+
+    def remove(self, filenode, **kwargs):
+        """
+        Commit api function that will remove given ``FileNode`` into this
+        repository.
+
+        :raises ``EmptyRepositoryError``: if there are no changesets yet
+        :raises ``NodeDoesNotExistError``: if there is no file with given path
+        """
+        raise NotImplementedError
+
+    def commit(self, message, **kwargs):
+        """
+        Persists current changes made on this repository and returns newly
+        created changeset.
+
+        :raises ``NothingChangedError``: if no changes has been made
+        """
+        raise NotImplementedError
+
+    def get_state(self):
+        """
+        Returns dictionary with ``added``, ``changed`` and ``removed`` lists
+        containing ``FileNode`` objects.
+        """
+        raise NotImplementedError
+
+    def get_config_value(self, section, name, config_file=None):
+        """
+        Returns configuration value for a given [``section``] and ``name``.
+
+        :param section: Section we want to retrieve value from
+        :param name: Name of configuration we want to retrieve
+        :param config_file: A path to file which should be used to retrieve
+          configuration from (might also be a list of file paths)
+        """
+        raise NotImplementedError
+
+    def get_user_name(self, config_file=None):
+        """
+        Returns user's name from global configuration file.
+
+        :param config_file: A path to file which should be used to retrieve
+          configuration from (might also be a list of file paths)
+        """
+        raise NotImplementedError
+
+    def get_user_email(self, config_file=None):
+        """
+        Returns user's email from global configuration file.
+
+        :param config_file: A path to file which should be used to retrieve
+          configuration from (might also be a list of file paths)
+        """
+        raise NotImplementedError
+
+    # =========== #
+    # WORKDIR API #
+    # =========== #
+
+    @LazyProperty
+    def workdir(self):
+        """
+        Returns ``Workdir`` instance for this repository.
+        """
+        raise NotImplementedError
+
+
+class BaseChangeset(object):
+    """
+    Each backend should implement it's changeset representation.
+
+    **Attributes**
+
+        ``repository``
+            repository object within which changeset exists
+
+        ``id``
+            may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
+
+        ``raw_id``
+            raw changeset representation (i.e. full 40 length sha for git
+            backend)
+
+        ``short_id``
+            shortened (if apply) version of ``raw_id``; it would be simple
+            shortcut for ``raw_id[:12]`` for git/mercurial backends or same
+            as ``raw_id`` for subversion
+
+        ``revision``
+            revision number as integer
+
+        ``files``
+            list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
+
+        ``dirs``
+            list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
+
+        ``nodes``
+            combined list of ``Node`` objects
+
+        ``author``
+            author of the changeset, as unicode
+
+        ``message``
+            message of the changeset, as unicode
+
+        ``parents``
+            list of parent changesets
+
+        ``last``
+            ``True`` if this is last changeset in repository, ``False``
+            otherwise; trying to access this attribute while there is no
+            changesets would raise ``EmptyRepositoryError``
+    """
+    def __str__(self):
+        return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
+            self.short_id)
+
+    def __repr__(self):
+        return self.__str__()
+
+    def __unicode__(self):
+        return u'%s:%s' % (self.revision, self.short_id)
+
+    def __eq__(self, other):
+        return self.raw_id == other.raw_id
+
+    @LazyProperty
+    def last(self):
+        if self.repository is None:
+            raise ChangesetError("Cannot check if it's most recent revision")
+        return self.raw_id == self.repository.revisions[-1]
+
+    @LazyProperty
+    def parents(self):
+        """
+        Returns list of parents changesets.
+        """
+        raise NotImplementedError
+
+    @LazyProperty
+    def id(self):
+        """
+        Returns string identifying this changeset.
+        """
+        raise NotImplementedError
+
+    @LazyProperty
+    def raw_id(self):
+        """
+        Returns raw string identifying this changeset.
+        """
+        raise NotImplementedError
+
+    @LazyProperty
+    def short_id(self):
+        """
+        Returns shortened version of ``raw_id`` attribute, as string,
+        identifying this changeset, useful for web representation.
+        """
+        raise NotImplementedError
+
+    @LazyProperty
+    def revision(self):
+        """
+        Returns integer identifying this changeset.
+
+        """
+        raise NotImplementedError
+
+    @LazyProperty
+    def author(self):
+        """
+        Returns Author for given commit
+        """
+
+        raise NotImplementedError
+
+    @LazyProperty
+    def author_name(self):
+        """
+        Returns Author name for given commit
+        """
+
+        return author_name(self.author)
+
+    @LazyProperty
+    def author_email(self):
+        """
+        Returns Author email address for given commit
+        """
+
+        return author_email(self.author)
+
+    def get_file_mode(self, path):
+        """
+        Returns stat mode of the file at the given ``path``.
+        """
+        raise NotImplementedError
+
+    def get_file_content(self, path):
+        """
+        Returns content of the file at the given ``path``.
+        """
+        raise NotImplementedError
+
+    def get_file_size(self, path):
+        """
+        Returns size of the file at the given ``path``.
+        """
+        raise NotImplementedError
+
+    def get_file_changeset(self, path):
+        """
+        Returns last commit of the file at the given ``path``.
+        """
+        raise NotImplementedError
+
+    def get_file_history(self, path):
+        """
+        Returns history of file as reversed list of ``Changeset`` objects for
+        which file at given ``path`` has been modified.
+        """
+        raise NotImplementedError
+
+    def get_nodes(self, path):
+        """
+        Returns combined ``DirNode`` and ``FileNode`` objects list representing
+        state of changeset at the given ``path``.
+
+        :raises ``ChangesetError``: if node at the given ``path`` is not
+          instance of ``DirNode``
+        """
+        raise NotImplementedError
+
+    def get_node(self, path):
+        """
+        Returns ``Node`` object from the given ``path``.
+
+        :raises ``NodeDoesNotExistError``: if there is no node at the given
+          ``path``
+        """
+        raise NotImplementedError
+
+    def fill_archive(self, stream=None, kind='tgz', prefix=None):
+        """
+        Fills up given stream.
+
+        :param stream: file like object.
+        :param kind: one of following: ``zip``, ``tar``, ``tgz``
+            or ``tbz2``. Default: ``tgz``.
+        :param prefix: name of root directory in archive.
+            Default is repository name and changeset's raw_id joined with dash.
+
+            repo-tip.<kind>
+        """
+
+        raise NotImplementedError
+
+    def get_chunked_archive(self, **kwargs):
+        """
+        Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
+
+        :param chunk_size: extra parameter which controls size of returned
+            chunks. Default:8k.
+        """
+
+        chunk_size = kwargs.pop('chunk_size', 8192)
+        stream = kwargs.get('stream')
+        self.fill_archive(**kwargs)
+        while True:
+            data = stream.read(chunk_size)
+            if not data:
+                break
+            yield data
+
+    @LazyProperty
+    def root(self):
+        """
+        Returns ``RootNode`` object for this changeset.
+        """
+        return self.get_node('')
+
+    def next(self, branch=None):
+        """
+        Returns next changeset from current, if branch is gives it will return
+        next changeset belonging to this branch
+
+        :param branch: show changesets within the given named branch
+        """
+        raise NotImplementedError
+
+    def prev(self, branch=None):
+        """
+        Returns previous changeset from current, if branch is gives it will
+        return previous changeset belonging to this branch
+
+        :param branch: show changesets within the given named branch
+        """
+        raise NotImplementedError
+
+    @LazyProperty
+    def added(self):
+        """
+        Returns list of added ``FileNode`` objects.
+        """
+        raise NotImplementedError
+
+    @LazyProperty
+    def changed(self):
+        """
+        Returns list of modified ``FileNode`` objects.
+        """
+        raise NotImplementedError
+
+    @LazyProperty
+    def removed(self):
+        """
+        Returns list of removed ``FileNode`` objects.
+        """
+        raise NotImplementedError
+
+    @LazyProperty
+    def size(self):
+        """
+        Returns total number of bytes from contents of all filenodes.
+        """
+        return sum((node.size for node in self.get_filenodes_generator()))
+
+    def walk(self, topurl=''):
+        """
+        Similar to os.walk method. Insted of filesystem it walks through
+        changeset starting at given ``topurl``.  Returns generator of tuples
+        (topnode, dirnodes, filenodes).
+        """
+        topnode = self.get_node(topurl)
+        yield (topnode, topnode.dirs, topnode.files)
+        for dirnode in topnode.dirs:
+            for tup in self.walk(dirnode.path):
+                yield tup
+
+    def get_filenodes_generator(self):
+        """
+        Returns generator that yields *all* file nodes.
+        """
+        for topnode, dirs, files in self.walk():
+            for node in files:
+                yield node
+
+    def as_dict(self):
+        """
+        Returns dictionary with changeset's attributes and their values.
+        """
+        data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
+            'revision', 'date', 'message'])
+        data['author'] = {'name': self.author_name, 'email': self.author_email}
+        data['added'] = [node.path for node in self.added]
+        data['changed'] = [node.path for node in self.changed]
+        data['removed'] = [node.path for node in self.removed]
+        return data
+
+
+class BaseWorkdir(object):
+    """
+    Working directory representation of single repository.
+
+    :attribute: repository: repository object of working directory
+    """
+
+    def __init__(self, repository):
+        self.repository = repository
+
+    def get_branch(self):
+        """
+        Returns name of current branch.
+        """
+        raise NotImplementedError
+
+    def get_changeset(self):
+        """
+        Returns current changeset.
+        """
+        raise NotImplementedError
+
+    def get_added(self):
+        """
+        Returns list of ``FileNode`` objects marked as *new* in working
+        directory.
+        """
+        raise NotImplementedError
+
+    def get_changed(self):
+        """
+        Returns list of ``FileNode`` objects *changed* in working directory.
+        """
+        raise NotImplementedError
+
+    def get_removed(self):
+        """
+        Returns list of ``RemovedFileNode`` objects marked as *removed* in
+        working directory.
+        """
+        raise NotImplementedError
+
+    def get_untracked(self):
+        """
+        Returns list of ``FileNode`` objects which are present within working
+        directory however are not tracked by repository.
+        """
+        raise NotImplementedError
+
+    def get_status(self):
+        """
+        Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
+        lists.
+        """
+        raise NotImplementedError
+
+    def commit(self, message, **kwargs):
+        """
+        Commits local (from working directory) changes and returns newly
+        created
+        ``Changeset``. Updates repository's ``revisions`` list.
+
+        :raises ``CommitError``: if any error occurs while committing
+        """
+        raise NotImplementedError
+
+    def update(self, revision=None):
+        """
+        Fetches content of the given revision and populates it within working
+        directory.
+        """
+        raise NotImplementedError
+
+    def checkout_branch(self, branch=None):
+        """
+        Checks out ``branch`` or the backend's default branch.
+
+        Raises ``BranchDoesNotExistError`` if the branch does not exist.
+        """
+        raise NotImplementedError
+
+
+class BaseInMemoryChangeset(object):
+    """
+    Represents differences between repository's state (most recent head) and
+    changes made *in place*.
+
+    **Attributes**
+
+        ``repository``
+            repository object for this in-memory-changeset
+
+        ``added``
+            list of ``FileNode`` objects marked as *added*
+
+        ``changed``
+            list of ``FileNode`` objects marked as *changed*
+
+        ``removed``
+            list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
+            *removed*
+
+        ``parents``
+            list of ``Changeset`` representing parents of in-memory changeset.
+            Should always be 2-element sequence.
+
+    """
+
+    def __init__(self, repository):
+        self.repository = repository
+        self.added = []
+        self.changed = []
+        self.removed = []
+        self.parents = []
+
+    def add(self, *filenodes):
+        """
+        Marks given ``FileNode`` objects as *to be committed*.
+
+        :raises ``NodeAlreadyExistsError``: if node with same path exists at
+          latest changeset
+        :raises ``NodeAlreadyAddedError``: if node with same path is already
+          marked as *added*
+        """
+        # Check if not already marked as *added* first
+        for node in filenodes:
+            if node.path in (n.path for n in self.added):
+                raise NodeAlreadyAddedError("Such FileNode %s is already "
+                    "marked for addition" % node.path)
+        for node in filenodes:
+            self.added.append(node)
+
+    def change(self, *filenodes):
+        """
+        Marks given ``FileNode`` objects to be *changed* in next commit.
+
+        :raises ``EmptyRepositoryError``: if there are no changesets yet
+        :raises ``NodeAlreadyExistsError``: if node with same path is already
+          marked to be *changed*
+        :raises ``NodeAlreadyRemovedError``: if node with same path is already
+          marked to be *removed*
+        :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
+          changeset
+        :raises ``NodeNotChangedError``: if node hasn't really be changed
+        """
+        for node in filenodes:
+            if node.path in (n.path for n in self.removed):
+                raise NodeAlreadyRemovedError("Node at %s is already marked "
+                    "as removed" % node.path)
+        try:
+            self.repository.get_changeset()
+        except EmptyRepositoryError:
+            raise EmptyRepositoryError("Nothing to change - try to *add* new "
+                "nodes rather than changing them")
+        for node in filenodes:
+            if node.path in (n.path for n in self.changed):
+                raise NodeAlreadyChangedError("Node at '%s' is already "
+                    "marked as changed" % node.path)
+            self.changed.append(node)
+
+    def remove(self, *filenodes):
+        """
+        Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
+        *removed* in next commit.
+
+        :raises ``NodeAlreadyRemovedError``: if node has been already marked to
+          be *removed*
+        :raises ``NodeAlreadyChangedError``: if node has been already marked to
+          be *changed*
+        """
+        for node in filenodes:
+            if node.path in (n.path for n in self.removed):
+                raise NodeAlreadyRemovedError("Node is already marked to "
+                    "for removal at %s" % node.path)
+            if node.path in (n.path for n in self.changed):
+                raise NodeAlreadyChangedError("Node is already marked to "
+                    "be changed at %s" % node.path)
+            # We only mark node as *removed* - real removal is done by
+            # commit method
+            self.removed.append(node)
+
+    def reset(self):
+        """
+        Resets this instance to initial state (cleans ``added``, ``changed``
+        and ``removed`` lists).
+        """
+        self.added = []
+        self.changed = []
+        self.removed = []
+        self.parents = []
+
+    def get_ipaths(self):
+        """
+        Returns generator of paths from nodes marked as added, changed or
+        removed.
+        """
+        for node in chain(self.added, self.changed, self.removed):
+            yield node.path
+
+    def get_paths(self):
+        """
+        Returns list of paths from nodes marked as added, changed or removed.
+        """
+        return list(self.get_ipaths())
+
+    def check_integrity(self, parents=None):
+        """
+        Checks in-memory changeset's integrity. Also, sets parents if not
+        already set.
+
+        :raises CommitError: if any error occurs (i.e.
+          ``NodeDoesNotExistError``).
+        """
+        if not self.parents:
+            parents = parents or []
+            if len(parents) == 0:
+                try:
+                    parents = [self.repository.get_changeset(), None]
+                except EmptyRepositoryError:
+                    parents = [None, None]
+            elif len(parents) == 1:
+                parents += [None]
+            self.parents = parents
+
+        # Local parents, only if not None
+        parents = [p for p in self.parents if p]
+
+        # Check nodes marked as added
+        for p in parents:
+            for node in self.added:
+                try:
+                    p.get_node(node.path)
+                except NodeDoesNotExistError:
+                    pass
+                else:
+                    raise NodeAlreadyExistsError("Node at %s already exists "
+                        "at %s" % (node.path, p))
+
+        # Check nodes marked as changed
+        missing = set(self.changed)
+        not_changed = set(self.changed)
+        if self.changed and not parents:
+            raise NodeDoesNotExistError(str(self.changed[0].path))
+        for p in parents:
+            for node in self.changed:
+                try:
+                    old = p.get_node(node.path)
+                    missing.remove(node)
+                    if old.content != node.content:
+                        not_changed.remove(node)
+                except NodeDoesNotExistError:
+                    pass
+        if self.changed and missing:
+            raise NodeDoesNotExistError("Node at %s is missing "
+                "(parents: %s)" % (node.path, parents))
+
+        if self.changed and not_changed:
+            raise NodeNotChangedError("Node at %s wasn't actually changed "
+                "since parents' changesets: %s" % (not_changed.pop().path,
+                    parents)
+            )
+
+        # Check nodes marked as removed
+        if self.removed and not parents:
+            raise NodeDoesNotExistError("Cannot remove node at %s as there "
+                "were no parents specified" % self.removed[0].path)
+        really_removed = set()
+        for p in parents:
+            for node in self.removed:
+                try:
+                    p.get_node(node.path)
+                    really_removed.add(node)
+                except ChangesetError:
+                    pass
+        not_removed = set(self.removed) - really_removed
+        if not_removed:
+            raise NodeDoesNotExistError("Cannot remove node at %s from "
+                "following parents: %s" % (not_removed[0], parents))
+
+    def commit(self, message, author, parents=None, branch=None, date=None,
+            **kwargs):
+        """
+        Performs in-memory commit (doesn't check workdir in any way) and
+        returns newly created ``Changeset``. Updates repository's
+        ``revisions``.
+
+        .. note::
+            While overriding this method each backend's should call
+            ``self.check_integrity(parents)`` in the first place.
+
+        :param message: message of the commit
+        :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
+        :param parents: single parent or sequence of parents from which commit
+          would be derieved
+        :param date: ``datetime.datetime`` instance. Defaults to
+          ``datetime.datetime.now()``.
+        :param branch: branch name, as string. If none given, default backend's
+          branch would be used.
+
+        :raises ``CommitError``: if any error occurs while committing
+        """
+        raise NotImplementedError
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/git/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,9 @@
+from .repository import GitRepository
+from .changeset import GitChangeset
+from .inmemory import GitInMemoryChangeset
+from .workdir import GitWorkdir
+
+
+__all__ = [
+    'GitRepository', 'GitChangeset', 'GitInMemoryChangeset', 'GitWorkdir',
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/git/changeset.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,450 @@
+import re
+from itertools import chain
+from dulwich import objects
+from subprocess import Popen, PIPE
+from rhodecode.lib.vcs.conf import settings
+from rhodecode.lib.vcs.exceptions import RepositoryError
+from rhodecode.lib.vcs.exceptions import ChangesetError
+from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
+from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError
+from rhodecode.lib.vcs.backends.base import BaseChangeset
+from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, RemovedFileNode
+from rhodecode.lib.vcs.utils import safe_unicode
+from rhodecode.lib.vcs.utils import date_fromtimestamp
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+
+
+class GitChangeset(BaseChangeset):
+    """
+    Represents state of the repository at single revision.
+    """
+
+    def __init__(self, repository, revision):
+        self._stat_modes = {}
+        self.repository = repository
+        self.raw_id = revision
+        self.revision = repository.revisions.index(revision)
+
+        self.short_id = self.raw_id[:12]
+        self.id = self.raw_id
+        try:
+            commit = self.repository._repo.get_object(self.raw_id)
+        except KeyError:
+            raise RepositoryError("Cannot get object with id %s" % self.raw_id)
+        self._commit = commit
+        self._tree_id = commit.tree
+
+        try:
+            self.message = safe_unicode(commit.message[:-1])
+            # Always strip last eol
+        except UnicodeDecodeError:
+            self.message = commit.message[:-1].decode(commit.encoding
+                or 'utf-8')
+        #self.branch = None
+        self.tags = []
+        #tree = self.repository.get_object(self._tree_id)
+        self.nodes = {}
+        self._paths = {}
+
+    @LazyProperty
+    def author(self):
+        return safe_unicode(self._commit.committer)
+
+    @LazyProperty
+    def date(self):
+        return date_fromtimestamp(self._commit.commit_time,
+                                  self._commit.commit_timezone)
+
+    @LazyProperty
+    def status(self):
+        """
+        Returns modified, added, removed, deleted files for current changeset
+        """
+        return self.changed, self.added, self.removed
+
+    @LazyProperty
+    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/')]
+
+        for name, id in heads:
+            walker = self.repository._repo.object_store.get_graph_walker([id])
+            while True:
+                id = walker.next()
+                if not id:
+                    break
+                if id == self.id:
+                    return safe_unicode(name)
+        raise ChangesetError("This should not happen... Have you manually "
+            "change id of the changeset?")
+
+    def _fix_path(self, path):
+        """
+        Paths are stored without trailing slash so we need to get rid off it if
+        needed.
+        """
+        if path.endswith('/'):
+            path = path.rstrip('/')
+        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('/')
+            # set root tree
+            tree = self.repository._repo[self._commit.tree]
+            if path == '':
+                self._paths[''] = tree.id
+                return tree.id
+            splitted = path.split('/')
+            dirs, name = splitted[:-1], splitted[-1]
+            curdir = ''
+            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:
+                    # Update tree
+                    tree = self.repository._repo[dir_id]
+                    if not isinstance(tree, objects.Tree):
+                        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
+            if not path in self._paths:
+                raise NodeDoesNotExistError("There is no file nor directory "
+                    "at the given path %r at revision %r"
+                    % (path, self.short_id))
+        return self._paths[path]
+
+    def _get_kind(self, path):
+        id = self._get_id_for_path(path)
+        obj = self.repository._repo[id]
+        if isinstance(obj, objects.Blob):
+            return NodeKind.FILE
+        elif isinstance(obj, objects.Tree):
+            return NodeKind.DIR
+
+    def _get_file_nodes(self):
+        return chain(*(t[2] for t in self.walk()))
+
+    @LazyProperty
+    def parents(self):
+        """
+        Returns list of parents changesets.
+        """
+        return [self.repository.get_changeset(parent)
+            for parent in self._commit.parents]
+
+    def next(self, branch=None):
+
+        if branch and self.branch != branch:
+            raise VCSError('Branch option used on changeset not belonging '
+                           'to that branch')
+
+        def _next(changeset, branch):
+            try:
+                next_ = changeset.revision + 1
+                next_rev = changeset.repository.revisions[next_]
+            except IndexError:
+                raise ChangesetDoesNotExistError
+            cs = changeset.repository.get_changeset(next_rev)
+
+            if branch and branch != cs.branch:
+                return _next(cs, branch)
+
+            return cs
+
+        return _next(self, branch)
+
+    def prev(self, branch=None):
+        if branch and self.branch != branch:
+            raise VCSError('Branch option used on changeset not belonging '
+                           'to that branch')
+
+        def _prev(changeset, branch):
+            try:
+                prev_ = changeset.revision - 1
+                if prev_ < 0:
+                    raise IndexError
+                prev_rev = changeset.repository.revisions[prev_]
+            except IndexError:
+                raise ChangesetDoesNotExistError
+
+            cs = changeset.repository.get_changeset(prev_rev)
+
+            if branch and branch != cs.branch:
+                return _prev(cs, branch)
+
+            return cs
+
+        return _prev(self, branch)
+
+    def get_file_mode(self, path):
+        """
+        Returns stat mode of the file at the given ``path``.
+        """
+        # ensure path is traversed
+        self._get_id_for_path(path)
+        return self._stat_modes[path]
+
+    def get_file_content(self, path):
+        """
+        Returns content of the file at given ``path``.
+        """
+        id = self._get_id_for_path(path)
+        blob = self.repository._repo[id]
+        return blob.as_pretty_string()
+
+    def get_file_size(self, path):
+        """
+        Returns size of the file at given ``path``.
+        """
+        id = self._get_id_for_path(path)
+        blob = self.repository._repo[id]
+        return blob.raw_length()
+
+    def get_file_changeset(self, path):
+        """
+        Returns last commit of the file at the given ``path``.
+        """
+        node = self.get_node(path)
+        return node.history[0]
+
+    def get_file_history(self, path):
+        """
+        Returns history of file as reversed list of ``Changeset`` objects for
+        which file at given ``path`` has been modified.
+
+        TODO: This function now uses os underlying 'git' and 'grep' commands
+        which is generally not good. Should be replaced with algorithm
+        iterating commits.
+        """
+        cmd = 'log --name-status -p %s -- "%s" | grep "^commit"' \
+            % (self.id, path)
+        so, se = self.repository.run_git_command(cmd)
+        ids = re.findall(r'\w{40}', so)
+        return [self.repository.get_changeset(id) for id in ids]
+
+    def get_file_annotate(self, path):
+        """
+        Returns a list of three element tuples with lineno,changeset and line
+
+        TODO: This function now uses os underlying 'git' command which is
+        generally not good. Should be replaced with algorithm iterating
+        commits.
+        """
+        cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
+        # -l     ==> outputs long shas (and we need all 40 characters)
+        # --root ==> doesn't put '^' character for bounderies
+        # -r sha ==> blames for the given revision
+        so, se = self.repository.run_git_command(cmd)
+        annotate = []
+        for i, blame_line in enumerate(so.split('\n')[:-1]):
+            ln_no = i + 1
+            id, line = re.split(r' \(.+?\) ', blame_line, 1)
+            annotate.append((ln_no, self.repository.get_changeset(id), line))
+        return annotate
+
+    def fill_archive(self, stream=None, kind='tgz', prefix=None,
+                     subrepos=False):
+        """
+        Fills up given stream.
+
+        :param stream: file like object.
+        :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
+            Default: ``tgz``.
+        :param prefix: name of root directory in archive.
+            Default is repository name and changeset's raw_id joined with dash
+            (``repo-tip.<KIND>``).
+        :param subrepos: include subrepos in this archive.
+
+        :raise ImproperArchiveTypeError: If given kind is wrong.
+        :raise VcsError: If given stream is None
+
+        """
+        allowed_kinds = settings.ARCHIVE_SPECS.keys()
+        if kind not in allowed_kinds:
+            raise ImproperArchiveTypeError('Archive kind not supported use one'
+                'of %s', allowed_kinds)
+
+        if prefix is None:
+            prefix = '%s-%s' % (self.repository.name, self.short_id)
+        elif prefix.startswith('/'):
+            raise VCSError("Prefix cannot start with leading slash")
+        elif prefix.strip() == '':
+            raise VCSError("Prefix cannot be empty")
+
+        if kind == 'zip':
+            frmt = 'zip'
+        else:
+            frmt = 'tar'
+        cmd = 'git archive --format=%s --prefix=%s/ %s' % (frmt, prefix,
+            self.raw_id)
+        if kind == 'tgz':
+            cmd += ' | gzip -9'
+        elif kind == 'tbz2':
+            cmd += ' | bzip2 -9'
+
+        if stream is None:
+            raise VCSError('You need to pass in a valid stream for filling'
+                           ' with archival data')
+        popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
+            cwd=self.repository.path)
+
+        buffer_size = 1024 * 8
+        chunk = popen.stdout.read(buffer_size)
+        while chunk:
+            stream.write(chunk)
+            chunk = popen.stdout.read(buffer_size)
+        # Make sure all descriptors would be read
+        popen.communicate()
+
+    def get_nodes(self, path):
+        if self._get_kind(path) != NodeKind.DIR:
+            raise ChangesetError("Directory does not exist for revision %r at "
+                " %r" % (self.revision, path))
+        path = self._fix_path(path)
+        id = self._get_id_for_path(path)
+        tree = self.repository._repo[id]
+        dirnodes = []
+        filenodes = []
+        for name, stat, id in tree.iteritems():
+            obj = self.repository._repo.get_object(id)
+            if path != '':
+                obj_path = '/'.join((path, name))
+            else:
+                obj_path = name
+            if obj_path not in self._stat_modes:
+                self._stat_modes[obj_path] = stat
+            if isinstance(obj, objects.Tree):
+                dirnodes.append(DirNode(obj_path, changeset=self))
+            elif isinstance(obj, objects.Blob):
+                filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
+            else:
+                raise ChangesetError("Requested object should be Tree "
+                                     "or Blob, is %r" % type(obj))
+        nodes = dirnodes + filenodes
+        for node in nodes:
+            if not node.path in self.nodes:
+                self.nodes[node.path] = node
+        nodes.sort()
+        return nodes
+
+    def get_node(self, path):
+        if isinstance(path, unicode):
+            path = path.encode('utf-8')
+        path = self._fix_path(path)
+        if not path in self.nodes:
+            try:
+                id = self._get_id_for_path(path)
+            except ChangesetError:
+                raise NodeDoesNotExistError("Cannot find one of parents' "
+                    "directories for a given path: %s" % path)
+            obj = self.repository._repo.get_object(id)
+            if isinstance(obj, objects.Tree):
+                if path == '':
+                    node = RootNode(changeset=self)
+                else:
+                    node = DirNode(path, changeset=self)
+                node._tree = obj
+            elif isinstance(obj, objects.Blob):
+                node = FileNode(path, changeset=self)
+                node._blob = obj
+            else:
+                raise NodeDoesNotExistError("There is no file nor directory "
+                    "at the given path %r at revision %r"
+                    % (path, self.short_id))
+            # cache node
+            self.nodes[path] = node
+        return self.nodes[path]
+
+    @LazyProperty
+    def affected_files(self):
+        """
+        Get's a fast accessible file changes for given changeset
+        """
+
+        return self.added + self.changed
+
+    @LazyProperty
+    def _diff_name_status(self):
+        output = []
+        for parent in self.parents:
+            cmd = 'diff --name-status %s %s' % (parent.raw_id, self.raw_id)
+            so, se = self.repository.run_git_command(cmd)
+            output.append(so.strip())
+        return '\n'.join(output)
+
+    def _get_paths_for_status(self, status):
+        """
+        Returns sorted list of paths for given ``status``.
+
+        :param status: one of: *added*, *modified* or *deleted*
+        """
+        paths = set()
+        char = status[0].upper()
+        for line in self._diff_name_status.splitlines():
+            if not line:
+                continue
+            if line.startswith(char):
+                splitted = line.split(char,1)
+                if not len(splitted) == 2:
+                    raise VCSError("Couldn't parse diff result:\n%s\n\n and "
+                        "particularly that line: %s" % (self._diff_name_status,
+                        line))
+                paths.add(splitted[1].strip())
+        return sorted(paths)
+
+    @LazyProperty
+    def added(self):
+        """
+        Returns list of added ``FileNode`` objects.
+        """
+        if not self.parents:
+            return list(self._get_file_nodes())
+        return [self.get_node(path) for path in self._get_paths_for_status('added')]
+
+    @LazyProperty
+    def changed(self):
+        """
+        Returns list of modified ``FileNode`` objects.
+        """
+        if not self.parents:
+            return []
+        return [self.get_node(path) for path in self._get_paths_for_status('modified')]
+
+    @LazyProperty
+    def removed(self):
+        """
+        Returns list of removed ``FileNode`` objects.
+        """
+        if not self.parents:
+            return []
+        return [RemovedFileNode(path) for path in self._get_paths_for_status('deleted')]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/git/config.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,347 @@
+# config.py - Reading and writing Git config files
+# Copyright (C) 2011 Jelmer Vernooij <jelmer@samba.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 2
+# of the License or (at your option) a later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.
+
+"""Reading and writing Git configuration files.
+
+TODO:
+ * preserve formatting when updating configuration files
+ * treat subsection names as case-insensitive for [branch.foo] style
+   subsections
+"""
+
+# Taken from dulwich not yet released 0.8.3 version (until it is actually
+# released)
+
+import errno
+import os
+import re
+
+from dulwich.file import GitFile
+
+
+class Config(object):
+    """A Git configuration."""
+
+    def get(self, section, name):
+        """Retrieve the contents of a configuration setting.
+
+        :param section: Tuple with section name and optional subsection namee
+        :param subsection: Subsection name
+        :return: Contents of the setting
+        :raise KeyError: if the value is not set
+        """
+        raise NotImplementedError(self.get)
+
+    def get_boolean(self, section, name, default=None):
+        """Retrieve a configuration setting as boolean.
+
+        :param section: Tuple with section name and optional subsection namee
+        :param name: Name of the setting, including section and possible
+            subsection.
+        :return: Contents of the setting
+        :raise KeyError: if the value is not set
+        """
+        try:
+            value = self.get(section, name)
+        except KeyError:
+            return default
+        if value.lower() == "true":
+            return True
+        elif value.lower() == "false":
+            return False
+        raise ValueError("not a valid boolean string: %r" % value)
+
+    def set(self, section, name, value):
+        """Set a configuration value.
+
+        :param name: Name of the configuration value, including section
+            and optional subsection
+        :param: Value of the setting
+        """
+        raise NotImplementedError(self.set)
+
+
+class ConfigDict(Config):
+    """Git configuration stored in a dictionary."""
+
+    def __init__(self, values=None):
+        """Create a new ConfigDict."""
+        if values is None:
+            values = {}
+        self._values = values
+
+    def __repr__(self):
+        return "%s(%r)" % (self.__class__.__name__, self._values)
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, self.__class__) and
+            other._values == self._values)
+
+    @classmethod
+    def _parse_setting(cls, name):
+        parts = name.split(".")
+        if len(parts) == 3:
+            return (parts[0], parts[1], parts[2])
+        else:
+            return (parts[0], None, parts[1])
+
+    def get(self, section, name):
+        if isinstance(section, basestring):
+            section = (section, )
+        if len(section) > 1:
+            try:
+                return self._values[section][name]
+            except KeyError:
+                pass
+        return self._values[(section[0],)][name]
+
+    def set(self, section, name, value):
+        if isinstance(section, basestring):
+            section = (section, )
+        self._values.setdefault(section, {})[name] = value
+
+
+def _format_string(value):
+    if (value.startswith(" ") or
+        value.startswith("\t") or
+        value.endswith(" ") or
+        value.endswith("\t")):
+        return '"%s"' % _escape_value(value)
+    return _escape_value(value)
+
+
+def _parse_string(value):
+    value = value.strip()
+    ret = []
+    block = []
+    in_quotes = False
+    for c in value:
+        if c == "\"":
+            in_quotes = (not in_quotes)
+            ret.append(_unescape_value("".join(block)))
+            block = []
+        elif c in ("#", ";") and not in_quotes:
+            # the rest of the line is a comment
+            break
+        else:
+            block.append(c)
+
+    if in_quotes:
+        raise ValueError("value starts with quote but lacks end quote")
+
+    ret.append(_unescape_value("".join(block)).rstrip())
+
+    return "".join(ret)
+
+
+def _unescape_value(value):
+    """Unescape a value."""
+    def unescape(c):
+        return {
+            "\\\\": "\\",
+            "\\\"": "\"",
+            "\\n": "\n",
+            "\\t": "\t",
+            "\\b": "\b",
+            }[c.group(0)]
+    return re.sub(r"(\\.)", unescape, value)
+
+
+def _escape_value(value):
+    """Escape a value."""
+    return value.replace("\\", "\\\\").replace("\n", "\\n")\
+            .replace("\t", "\\t").replace("\"", "\\\"")
+
+
+def _check_variable_name(name):
+    for c in name:
+        if not c.isalnum() and c != '-':
+            return False
+    return True
+
+
+def _check_section_name(name):
+    for c in name:
+        if not c.isalnum() and c not in ('-', '.'):
+            return False
+    return True
+
+
+def _strip_comments(line):
+    line = line.split("#")[0]
+    line = line.split(";")[0]
+    return line
+
+
+class ConfigFile(ConfigDict):
+    """A Git configuration file, like .git/config or ~/.gitconfig.
+    """
+
+    @classmethod
+    def from_file(cls, f):
+        """Read configuration from a file-like object."""
+        ret = cls()
+        section = None
+        setting = None
+        for lineno, line in enumerate(f.readlines()):
+            line = line.lstrip()
+            if setting is None:
+                if _strip_comments(line).strip() == "":
+                    continue
+                if line[0] == "[":
+                    line = _strip_comments(line).rstrip()
+                    if line[-1] != "]":
+                        raise ValueError("expected trailing ]")
+                    key = line.strip()
+                    pts = key[1:-1].split(" ", 1)
+                    pts[0] = pts[0].lower()
+                    if len(pts) == 2:
+                        if pts[1][0] != "\"" or pts[1][-1] != "\"":
+                            raise ValueError(
+                                "Invalid subsection " + pts[1])
+                        else:
+                            pts[1] = pts[1][1:-1]
+                        if not _check_section_name(pts[0]):
+                            raise ValueError("invalid section name %s" %
+                                             pts[0])
+                        section = (pts[0], pts[1])
+                    else:
+                        if not _check_section_name(pts[0]):
+                            raise ValueError("invalid section name %s" %
+                                    pts[0])
+                        pts = pts[0].split(".", 1)
+                        if len(pts) == 2:
+                            section = (pts[0], pts[1])
+                        else:
+                            section = (pts[0], )
+                    ret._values[section] = {}
+                else:
+                    if section is None:
+                        raise ValueError("setting %r without section" % line)
+                    try:
+                        setting, value = line.split("=", 1)
+                    except ValueError:
+                        setting = line
+                        value = "true"
+                    setting = setting.strip().lower()
+                    if not _check_variable_name(setting):
+                        raise ValueError("invalid variable name %s" % setting)
+                    if value.endswith("\\\n"):
+                        value = value[:-2]
+                        continuation = True
+                    else:
+                        continuation = False
+                    value = _parse_string(value)
+                    ret._values[section][setting] = value
+                    if not continuation:
+                        setting = None
+            else:  # continuation line
+                if line.endswith("\\\n"):
+                    line = line[:-2]
+                    continuation = True
+                else:
+                    continuation = False
+                value = _parse_string(line)
+                ret._values[section][setting] += value
+                if not continuation:
+                    setting = None
+        return ret
+
+    @classmethod
+    def from_path(cls, path):
+        """Read configuration from a file on disk."""
+        f = GitFile(path, 'rb')
+        try:
+            ret = cls.from_file(f)
+            ret.path = path
+            return ret
+        finally:
+            f.close()
+
+    def write_to_path(self, path=None):
+        """Write configuration to a file on disk."""
+        if path is None:
+            path = self.path
+        f = GitFile(path, 'wb')
+        try:
+            self.write_to_file(f)
+        finally:
+            f.close()
+
+    def write_to_file(self, f):
+        """Write configuration to a file-like object."""
+        for section, values in self._values.iteritems():
+            try:
+                section_name, subsection_name = section
+            except ValueError:
+                (section_name, ) = section
+                subsection_name = None
+            if subsection_name is None:
+                f.write("[%s]\n" % section_name)
+            else:
+                f.write("[%s \"%s\"]\n" % (section_name, subsection_name))
+            for key, value in values.iteritems():
+                f.write("%s = %s\n" % (key, _escape_value(value)))
+
+
+class StackedConfig(Config):
+    """Configuration which reads from multiple config files.."""
+
+    def __init__(self, backends, writable=None):
+        self.backends = backends
+        self.writable = writable
+
+    def __repr__(self):
+        return "<%s for %r>" % (self.__class__.__name__, self.backends)
+
+    @classmethod
+    def default_backends(cls):
+        """Retrieve the default configuration.
+
+        This will look in the repository configuration (if for_path is
+        specified), the users' home directory and the system
+        configuration.
+        """
+        paths = []
+        paths.append(os.path.expanduser("~/.gitconfig"))
+        paths.append("/etc/gitconfig")
+        backends = []
+        for path in paths:
+            try:
+                cf = ConfigFile.from_path(path)
+            except (IOError, OSError), e:
+                if e.errno != errno.ENOENT:
+                    raise
+                else:
+                    continue
+            backends.append(cf)
+        return backends
+
+    def get(self, section, name):
+        for backend in self.backends:
+            try:
+                return backend.get(section, name)
+            except KeyError:
+                pass
+        raise KeyError(name)
+
+    def set(self, section, name, value):
+        if self.writable is None:
+            raise NotImplementedError(self.set)
+        return self.writable.set(section, name, value)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/git/inmemory.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,192 @@
+import time
+import datetime
+import posixpath
+from dulwich import objects
+from dulwich.repo import Repo
+from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset
+from rhodecode.lib.vcs.exceptions import RepositoryError
+
+
+class GitInMemoryChangeset(BaseInMemoryChangeset):
+
+    def commit(self, message, author, parents=None, branch=None, date=None,
+            **kwargs):
+        """
+        Performs in-memory commit (doesn't check workdir in any way) and
+        returns newly created ``Changeset``. Updates repository's
+        ``revisions``.
+
+        :param message: message of the commit
+        :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
+        :param parents: single parent or sequence of parents from which commit
+          would be derieved
+        :param date: ``datetime.datetime`` instance. Defaults to
+          ``datetime.datetime.now()``.
+        :param branch: branch name, as string. If none given, default backend's
+          branch would be used.
+
+        :raises ``CommitError``: if any error occurs while committing
+        """
+        self.check_integrity(parents)
+
+        from .repository import GitRepository
+        if branch is None:
+            branch = GitRepository.DEFAULT_BRANCH_NAME
+
+        repo = self.repository._repo
+        object_store = repo.object_store
+
+        ENCODING = "UTF-8"
+        DIRMOD = 040000
+
+        # Create tree and populates it with blobs
+        commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or\
+            objects.Tree()
+        for node in self.added + self.changed:
+            # Compute subdirs if needed
+            dirpath, nodename = posixpath.split(node.path)
+            dirnames = dirpath and dirpath.split('/') or []
+            parent = commit_tree
+            ancestors = [('', parent)]
+
+            # Tries to dig for the deepest existing tree
+            while dirnames:
+                curdir = dirnames.pop(0)
+                try:
+                    dir_id = parent[curdir][1]
+                except KeyError:
+                    # put curdir back into dirnames and stops
+                    dirnames.insert(0, curdir)
+                    break
+                else:
+                    # If found, updates parent
+                    parent = self.repository._repo[dir_id]
+                    ancestors.append((curdir, parent))
+            # Now parent is deepest exising tree and we need to create subtrees
+            # for dirnames (in reverse order) [this only applies for nodes from added]
+            new_trees = []
+            blob = objects.Blob.from_string(node.content.encode(ENCODING))
+            node_path = node.name.encode(ENCODING)
+            if dirnames:
+                # If there are trees which should be created we need to build
+                # them now (in reverse order)
+                reversed_dirnames = list(reversed(dirnames))
+                curtree = objects.Tree()
+                curtree[node_path] = node.mode, blob.id
+                new_trees.append(curtree)
+                for dirname in reversed_dirnames[:-1]:
+                    newtree = objects.Tree()
+                    #newtree.add(DIRMOD, dirname, curtree.id)
+                    newtree[dirname] = DIRMOD, curtree.id
+                    new_trees.append(newtree)
+                    curtree = newtree
+                parent[reversed_dirnames[-1]] = DIRMOD, curtree.id
+            else:
+                parent.add(node.mode, node_path, blob.id)
+            new_trees.append(parent)
+            # Update ancestors
+            for parent, tree, path in reversed([(a[1], b[1], b[0]) for a, b in
+                zip(ancestors, ancestors[1:])]):
+                parent[path] = DIRMOD, tree.id
+                object_store.add_object(tree)
+
+            object_store.add_object(blob)
+            for tree in new_trees:
+                object_store.add_object(tree)
+        for node in self.removed:
+            paths = node.path.split('/')
+            tree = commit_tree
+            trees = [tree]
+            # Traverse deep into the forest...
+            for path in paths:
+                try:
+                    obj = self.repository._repo[tree[path][1]]
+                    if isinstance(obj, objects.Tree):
+                        trees.append(obj)
+                        tree = obj
+                except KeyError:
+                    break
+            # Cut down the blob and all rotten trees on the way back...
+            for path, tree in reversed(zip(paths, trees)):
+                del tree[path]
+                if tree:
+                    # This tree still has elements - don't remove it or any
+                    # of it's parents
+                    break
+
+        object_store.add_object(commit_tree)
+
+        # Create commit
+        commit = objects.Commit()
+        commit.tree = commit_tree.id
+        commit.parents = [p._commit.id for p in self.parents if p]
+        commit.author = commit.committer = author
+        commit.encoding = ENCODING
+        commit.message = message + ' '
+
+        # Compute date
+        if date is None:
+            date = time.time()
+        elif isinstance(date, datetime.datetime):
+            date = time.mktime(date.timetuple())
+
+        author_time = kwargs.pop('author_time', date)
+        commit.commit_time = int(date)
+        commit.author_time = int(author_time)
+        tz = time.timezone
+        author_tz = kwargs.pop('author_timezone', tz)
+        commit.commit_timezone = tz
+        commit.author_timezone = author_tz
+
+        object_store.add_object(commit)
+
+        ref = 'refs/heads/%s' % branch
+        repo.refs[ref] = commit.id
+        repo.refs.set_symbolic_ref('HEAD', ref)
+
+        # Update vcs repository object & recreate dulwich repo
+        self.repository.revisions.append(commit.id)
+        self.repository._repo = Repo(self.repository.path)
+        tip = self.repository.get_changeset()
+        self.reset()
+        return tip
+
+    def _get_missing_trees(self, path, root_tree):
+        """
+        Creates missing ``Tree`` objects for the given path.
+
+        :param path: path given as a string. It may be a path to a file node
+          (i.e. ``foo/bar/baz.txt``) or directory path - in that case it must
+          end with slash (i.e. ``foo/bar/``).
+        :param root_tree: ``dulwich.objects.Tree`` object from which we start
+          traversing (should be commit's root tree)
+        """
+        dirpath = posixpath.split(path)[0]
+        dirs = dirpath.split('/')
+        if not dirs or dirs == ['']:
+            return []
+
+        def get_tree_for_dir(tree, dirname):
+            for name, mode, id in tree.iteritems():
+                if name == dirname:
+                    obj = self.repository._repo[id]
+                    if isinstance(obj, objects.Tree):
+                        return obj
+                    else:
+                        raise RepositoryError("Cannot create directory %s "
+                        "at tree %s as path is occupied and is not a "
+                        "Tree" % (dirname, tree))
+            return None
+
+        trees = []
+        parent = root_tree
+        for dirname in dirs:
+            tree = get_tree_for_dir(parent, dirname)
+            if tree is None:
+                tree = objects.Tree()
+                dirmode = 040000
+                parent.add(dirmode, dirname, tree.id)
+                parent = tree
+            # Always append tree
+            trees.append(tree)
+        return trees
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/git/repository.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,508 @@
+# -*- coding: utf-8 -*-
+"""
+    vcs.backends.git
+    ~~~~~~~~~~~~~~~~
+
+    Git backend implementation.
+
+    :created_on: Apr 8, 2010
+    :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
+"""
+
+import os
+import re
+import time
+import posixpath
+from dulwich.repo import Repo, NotGitRepository
+#from dulwich.config import ConfigFile
+from string import Template
+from subprocess import Popen, PIPE
+from rhodecode.lib.vcs.backends.base import BaseRepository
+from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
+from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
+from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
+from rhodecode.lib.vcs.exceptions import RepositoryError
+from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
+from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
+from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
+from rhodecode.lib.vcs.utils.paths import abspath
+from rhodecode.lib.vcs.utils.paths import get_user_home
+from .workdir import GitWorkdir
+from .changeset import GitChangeset
+from .inmemory import GitInMemoryChangeset
+from .config import ConfigFile
+
+
+class GitRepository(BaseRepository):
+    """
+    Git repository backend.
+    """
+    DEFAULT_BRANCH_NAME = 'master'
+    scm = 'git'
+
+    def __init__(self, repo_path, create=False, src_url=None,
+                 update_after_clone=False, bare=False):
+
+        self.path = abspath(repo_path)
+        self._repo = self._get_repo(create, src_url, update_after_clone, bare)
+        try:
+            self.head = self._repo.head()
+        except KeyError:
+            self.head = None
+
+        self._config_files = [
+            bare and abspath(self.path, 'config') or abspath(self.path, '.git',
+                'config'),
+            abspath(get_user_home(), '.gitconfig'),
+        ]
+
+    @LazyProperty
+    def revisions(self):
+        """
+        Returns list of revisions' ids, in ascending order.  Being lazy
+        attribute allows external tools to inject shas from cache.
+        """
+        return self._get_all_revisions()
+
+    def run_git_command(self, cmd):
+        """
+        Runs given ``cmd`` as git command and returns tuple
+        (returncode, stdout, stderr).
+
+        .. note::
+           This method exists only until log/blame functionality is implemented
+           at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
+           os command's output is road to hell...
+
+        :param cmd: git command to be executed
+        """
+        #cmd = '(cd %s && git %s)' % (self.path, cmd)
+        if isinstance(cmd, basestring):
+            cmd = 'git %s' % cmd
+        else:
+            cmd = ['git'] + cmd
+        try:
+            opts = dict(
+                shell=isinstance(cmd, basestring),
+                stdout=PIPE,
+                stderr=PIPE)
+            if os.path.isdir(self.path):
+                opts['cwd'] = self.path
+            p = Popen(cmd, **opts)
+        except OSError, err:
+            raise RepositoryError("Couldn't run git command (%s).\n"
+                "Original error was:%s" % (cmd, err))
+        so, se = p.communicate()
+        if not se.startswith("fatal: bad default revision 'HEAD'") and \
+            p.returncode != 0:
+            raise RepositoryError("Couldn't run git command (%s).\n"
+                "stderr:\n%s" % (cmd, se))
+        return so, se
+
+    def _check_url(self, url):
+        """
+        Functon will check given url and try to verify if it's a valid
+        link. Sometimes it may happened that mercurial will issue basic
+        auth request that can cause whole API to hang when used from python
+        or other external calls.
+
+        On failures it'll raise urllib2.HTTPError
+        """
+
+        #TODO: implement this
+        pass
+
+    def _get_repo(self, create, src_url=None, update_after_clone=False,
+            bare=False):
+        if create and os.path.exists(self.path):
+            raise RepositoryError("Location already exist")
+        if src_url and not create:
+            raise RepositoryError("Create should be set to True if src_url is "
+                                  "given (clone operation creates repository)")
+        try:
+            if create and src_url:
+                self._check_url(src_url)
+                self.clone(src_url, update_after_clone, bare)
+                return Repo(self.path)
+            elif create:
+                os.mkdir(self.path)
+                if bare:
+                    return Repo.init_bare(self.path)
+                else:
+                    return Repo.init(self.path)
+            else:
+                return Repo(self.path)
+        except (NotGitRepository, OSError), err:
+            raise RepositoryError(err)
+
+    def _get_all_revisions(self):
+        cmd = 'rev-list --all --date-order'
+        try:
+            so, se = self.run_git_command(cmd)
+        except RepositoryError:
+            # Can be raised for empty repositories
+            return []
+        revisions = so.splitlines()
+        revisions.reverse()
+        return revisions
+
+    def _get_revision(self, revision):
+        """
+        For git backend we always return integer here. This way we ensure
+        that changset's revision attribute would become integer.
+        """
+        pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
+        is_bstr = lambda o: isinstance(o, (str, unicode))
+        is_null = lambda o: len(o) == revision.count('0')
+
+        if len(self.revisions) == 0:
+            raise EmptyRepositoryError("There are no changesets yet")
+
+        if revision in (None, '', 'tip', 'HEAD', 'head', -1):
+            revision = self.revisions[-1]
+
+        if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
+            or isinstance(revision, int) or is_null(revision)):
+            try:
+                revision = self.revisions[int(revision)]
+            except:
+                raise ChangesetDoesNotExistError("Revision %r does not exist "
+                    "for this repository %s" % (revision, self))
+
+        elif is_bstr(revision):
+            if not pattern.match(revision) or revision not in self.revisions:
+                raise ChangesetDoesNotExistError("Revision %r does not exist "
+                    "for this repository %s" % (revision, self))
+
+        # Ensure we return full id
+        if not pattern.match(str(revision)):
+            raise ChangesetDoesNotExistError("Given revision %r not recognized"
+                % revision)
+        return revision
+
+    def _get_archives(self, archive_name='tip'):
+
+        for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
+                yield {"type": i[0], "extension": i[1], "node": archive_name}
+
+    def _get_url(self, url):
+        """
+        Returns normalized url. If schema is not given, would fall to
+        filesystem (``file:///``) schema.
+        """
+        url = str(url)
+        if url != 'default' and not '://' in url:
+            url = ':///'.join(('file', url))
+        return url
+
+    @LazyProperty
+    def name(self):
+        return os.path.basename(self.path)
+
+    @LazyProperty
+    def last_change(self):
+        """
+        Returns last change made on this repository as datetime object
+        """
+        return date_fromtimestamp(self._get_mtime(), makedate()[1])
+
+    def _get_mtime(self):
+        try:
+            return time.mktime(self.get_changeset().date.timetuple())
+        except RepositoryError:
+            # fallback to filesystem
+            in_path = os.path.join(self.path, '.git', "index")
+            he_path = os.path.join(self.path, '.git', "HEAD")
+            if os.path.exists(in_path):
+                return os.stat(in_path).st_mtime
+            else:
+                return os.stat(he_path).st_mtime
+
+    @LazyProperty
+    def description(self):
+        undefined_description = u'unknown'
+        description_path = os.path.join(self.path, '.git', 'description')
+        if os.path.isfile(description_path):
+            return safe_unicode(open(description_path).read())
+        else:
+            return undefined_description
+
+    @LazyProperty
+    def contact(self):
+        undefined_contact = u'Unknown'
+        return undefined_contact
+
+    @property
+    def branches(self):
+        if not self.revisions:
+            return {}
+        refs = self._repo.refs.as_dict()
+        sortkey = lambda ctx: ctx[0]
+        _branches = [('/'.join(ref.split('/')[2:]), head)
+            for ref, head in refs.items()
+            if ref.startswith('refs/heads/') or
+            ref.startswith('refs/remotes/') and not ref.endswith('/HEAD')]
+        return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
+
+    def _get_tags(self):
+        if not self.revisions:
+            return {}
+        sortkey = lambda ctx: ctx[0]
+        _tags = [('/'.join(ref.split('/')[2:]), head) for ref, head in
+            self._repo.get_refs().items() if ref.startswith('refs/tags/')]
+        return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
+
+    @LazyProperty
+    def tags(self):
+        return self._get_tags()
+
+    def tag(self, name, user, revision=None, message=None, date=None,
+            **kwargs):
+        """
+        Creates and returns a tag for the given ``revision``.
+
+        :param name: name for new tag
+        :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
+        :param revision: changeset id for which new tag would be created
+        :param message: message of the tag's commit
+        :param date: date of tag's commit
+
+        :raises TagAlreadyExistError: if tag with same name already exists
+        """
+        if name in self.tags:
+            raise TagAlreadyExistError("Tag %s already exists" % name)
+        changeset = self.get_changeset(revision)
+        message = message or "Added tag %s for commit %s" % (name,
+            changeset.raw_id)
+        self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
+
+        self.tags = self._get_tags()
+        return changeset
+
+    def remove_tag(self, name, user, message=None, date=None):
+        """
+        Removes tag with the given ``name``.
+
+        :param name: name of the tag to be removed
+        :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
+        :param message: message of the tag's removal commit
+        :param date: date of tag's removal commit
+
+        :raises TagDoesNotExistError: if tag with given name does not exists
+        """
+        if name not in self.tags:
+            raise TagDoesNotExistError("Tag %s does not exist" % name)
+        tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
+        try:
+            os.remove(tagpath)
+            self.tags = self._get_tags()
+        except OSError, e:
+            raise RepositoryError(e.strerror)
+
+    def get_changeset(self, revision=None):
+        """
+        Returns ``GitChangeset`` object representing commit from git repository
+        at the given revision or head (most recent commit) if None given.
+        """
+        if isinstance(revision, GitChangeset):
+            return revision
+        revision = self._get_revision(revision)
+        changeset = GitChangeset(repository=self, revision=revision)
+        return changeset
+
+    def get_changesets(self, start=None, end=None, start_date=None,
+           end_date=None, branch_name=None, reverse=False):
+        """
+        Returns iterator of ``GitChangeset`` objects from start to end (both
+        are inclusive), in ascending date order (unless ``reverse`` is set).
+
+        :param start: changeset ID, as str; first returned changeset
+        :param end: changeset ID, as str; last returned changeset
+        :param start_date: if specified, changesets with commit date less than
+          ``start_date`` would be filtered out from returned set
+        :param end_date: if specified, changesets with commit date greater than
+          ``end_date`` would be filtered out from returned set
+        :param branch_name: if specified, changesets not reachable from given
+          branch would be filtered out from returned set
+        :param reverse: if ``True``, returned generator would be reversed
+          (meaning that returned changesets would have descending date order)
+
+        :raise BranchDoesNotExistError: If given ``branch_name`` does not
+            exist.
+        :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
+          ``end`` could not be found.
+
+        """
+        if branch_name and branch_name not in self.branches:
+            raise BranchDoesNotExistError("Branch '%s' not found" \
+                                          % branch_name)
+        # %H at format means (full) commit hash, initial hashes are retrieved
+        # in ascending date order
+        cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
+        cmd_params = {}
+        if start_date:
+            cmd_template += ' --since "$since"'
+            cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
+        if end_date:
+            cmd_template += ' --until "$until"'
+            cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
+        if branch_name:
+            cmd_template += ' $branch_name'
+            cmd_params['branch_name'] = branch_name
+        else:
+            cmd_template += ' --all'
+
+        cmd = Template(cmd_template).safe_substitute(**cmd_params)
+        revs = self.run_git_command(cmd)[0].splitlines()
+        start_pos = 0
+        end_pos = len(revs)
+        if start:
+            _start = self._get_revision(start)
+            try:
+                start_pos = revs.index(_start)
+            except ValueError:
+                pass
+
+        if end is not None:
+            _end = self._get_revision(end)
+            try:
+                end_pos = revs.index(_end)
+            except ValueError:
+                pass
+
+        if None not in [start, end] and start_pos > end_pos:
+            raise RepositoryError('start cannot be after end')
+
+        if end_pos is not None:
+            end_pos += 1
+
+        revs = revs[start_pos:end_pos]
+        if reverse:
+            revs = reversed(revs)
+        for rev in revs:
+            yield self.get_changeset(rev)
+
+    def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
+            context=3):
+        """
+        Returns (git like) *diff*, as plain text. Shows changes introduced by
+        ``rev2`` since ``rev1``.
+
+        :param rev1: Entry point from which diff is shown. Can be
+          ``self.EMPTY_CHANGESET`` - in this case, patch showing all
+          the changes since empty state of the repository until ``rev2``
+        :param rev2: Until which revision changes should be shown.
+        :param ignore_whitespace: If set to ``True``, would not show whitespace
+          changes. Defaults to ``False``.
+        :param context: How many lines before/after changed lines should be
+          shown. Defaults to ``3``.
+        """
+        flags = ['-U%s' % context]
+        if ignore_whitespace:
+            flags.append('-w')
+
+        if rev1 == self.EMPTY_CHANGESET:
+            rev2 = self.get_changeset(rev2).raw_id
+            cmd = ' '.join(['show'] + flags + [rev2])
+        else:
+            rev1 = self.get_changeset(rev1).raw_id
+            rev2 = self.get_changeset(rev2).raw_id
+            cmd = ' '.join(['diff'] + flags + [rev1, rev2])
+
+        if path:
+            cmd += ' -- "%s"' % path
+        stdout, stderr = self.run_git_command(cmd)
+        # If we used 'show' command, strip first few lines (until actual diff
+        # starts)
+        if rev1 == self.EMPTY_CHANGESET:
+            lines = stdout.splitlines()
+            x = 0
+            for line in lines:
+                if line.startswith('diff'):
+                    break
+                x += 1
+            # Append new line just like 'diff' command do
+            stdout = '\n'.join(lines[x:]) + '\n'
+        return stdout
+
+    @LazyProperty
+    def in_memory_changeset(self):
+        """
+        Returns ``GitInMemoryChangeset`` object for this repository.
+        """
+        return GitInMemoryChangeset(self)
+
+    def clone(self, url, update_after_clone=True, bare=False):
+        """
+        Tries to clone changes from external location.
+
+        :param update_after_clone: If set to ``False``, git won't checkout
+          working directory
+        :param bare: If set to ``True``, repository would be cloned into
+          *bare* git repository (no working directory at all).
+        """
+        url = self._get_url(url)
+        cmd = ['clone']
+        if bare:
+            cmd.append('--bare')
+        elif not update_after_clone:
+            cmd.append('--no-checkout')
+        cmd += ['--', '"%s"' % url, '"%s"' % self.path]
+        cmd = ' '.join(cmd)
+        # If error occurs run_git_command raises RepositoryError already
+        self.run_git_command(cmd)
+
+    @LazyProperty
+    def workdir(self):
+        """
+        Returns ``Workdir`` instance for this repository.
+        """
+        return GitWorkdir(self)
+
+    def get_config_value(self, section, name, config_file=None):
+        """
+        Returns configuration value for a given [``section``] and ``name``.
+
+        :param section: Section we want to retrieve value from
+        :param name: Name of configuration we want to retrieve
+        :param config_file: A path to file which should be used to retrieve
+          configuration from (might also be a list of file paths)
+        """
+        if config_file is None:
+            config_file = []
+        elif isinstance(config_file, basestring):
+            config_file = [config_file]
+
+        def gen_configs():
+            for path in config_file + self._config_files:
+                try:
+                    yield ConfigFile.from_path(path)
+                except (IOError, OSError, ValueError):
+                    continue
+
+        for config in gen_configs():
+            try:
+                return config.get(section, name)
+            except KeyError:
+                continue
+        return None
+
+    def get_user_name(self, config_file=None):
+        """
+        Returns user's name from global configuration file.
+
+        :param config_file: A path to file which should be used to retrieve
+          configuration from (might also be a list of file paths)
+        """
+        return self.get_config_value('user', 'name', config_file)
+
+    def get_user_email(self, config_file=None):
+        """
+        Returns user's email from global configuration file.
+
+        :param config_file: A path to file which should be used to retrieve
+          configuration from (might also be a list of file paths)
+        """
+        return self.get_config_value('user', 'email', config_file)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/git/workdir.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,31 @@
+import re
+from rhodecode.lib.vcs.backends.base import BaseWorkdir
+from rhodecode.lib.vcs.exceptions import RepositoryError
+from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
+
+
+class GitWorkdir(BaseWorkdir):
+
+    def get_branch(self):
+        headpath = self.repository._repo.refs.refpath('HEAD')
+        try:
+            content = open(headpath).read()
+            match = re.match(r'^ref: refs/heads/(?P<branch>.+)\n$', content)
+            if match:
+                return match.groupdict()['branch']
+            else:
+                raise RepositoryError("Couldn't compute workdir's branch")
+        except IOError:
+            # Try naive way...
+            raise RepositoryError("Couldn't compute workdir's branch")
+
+    def get_changeset(self):
+        return self.repository.get_changeset(
+            self.repository._repo.refs.as_dict().get('HEAD'))
+
+    def checkout_branch(self, branch=None):
+        if branch is None:
+            branch = self.repository.DEFAULT_BRANCH_NAME
+        if branch not in self.repository.branches:
+            raise BranchDoesNotExistError
+        self.repository.run_git_command(['checkout', branch])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/hg/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+"""
+    vcs.backends.hg
+    ~~~~~~~~~~~~~~~~
+
+    Mercurial backend implementation.
+
+    :created_on: Apr 8, 2010
+    :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
+"""
+
+from .repository import MercurialRepository
+from .changeset import MercurialChangeset
+from .inmemory import MercurialInMemoryChangeset
+from .workdir import MercurialWorkdir
+
+
+__all__ = [
+    'MercurialRepository', 'MercurialChangeset',
+    'MercurialInMemoryChangeset', 'MercurialWorkdir',
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/hg/changeset.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,338 @@
+import os
+import posixpath
+
+from rhodecode.lib.vcs.backends.base import BaseChangeset
+from rhodecode.lib.vcs.conf import settings
+from rhodecode.lib.vcs.exceptions import  ChangesetDoesNotExistError, \
+    ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
+from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, ChangedFileNodesGenerator, \
+    DirNode, FileNode, NodeKind, RemovedFileNodesGenerator, RootNode
+
+from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
+
+from ...utils.hgcompat import archival, hex
+
+
+class MercurialChangeset(BaseChangeset):
+    """
+    Represents state of the repository at the single revision.
+    """
+
+    def __init__(self, repository, revision):
+        self.repository = repository
+        self.raw_id = revision
+        self._ctx = repository._repo[revision]
+        self.revision = self._ctx._rev
+        self.nodes = {}
+
+    @LazyProperty
+    def tags(self):
+        return map(safe_unicode, self._ctx.tags())
+
+    @LazyProperty
+    def branch(self):
+        return  safe_unicode(self._ctx.branch())
+
+    @LazyProperty
+    def message(self):
+        return safe_unicode(self._ctx.description())
+
+    @LazyProperty
+    def author(self):
+        return safe_unicode(self._ctx.user())
+
+    @LazyProperty
+    def date(self):
+        return date_fromtimestamp(*self._ctx.date())
+
+    @LazyProperty
+    def status(self):
+        """
+        Returns modified, added, removed, deleted files for current changeset
+        """
+        return self.repository._repo.status(self._ctx.p1().node(),
+                                            self._ctx.node())
+
+    @LazyProperty
+    def _file_paths(self):
+        return list(self._ctx)
+
+    @LazyProperty
+    def _dir_paths(self):
+        p = list(set(get_dirs_for_path(*self._file_paths)))
+        p.insert(0, '')
+        return p
+
+    @LazyProperty
+    def _paths(self):
+        return self._dir_paths + self._file_paths
+
+    @LazyProperty
+    def id(self):
+        if self.last:
+            return u'tip'
+        return self.short_id
+
+    @LazyProperty
+    def short_id(self):
+        return self.raw_id[:12]
+
+    @LazyProperty
+    def parents(self):
+        """
+        Returns list of parents changesets.
+        """
+        return [self.repository.get_changeset(parent.rev())
+                for parent in self._ctx.parents() if parent.rev() >= 0]
+
+    def next(self, branch=None):
+
+        if branch and self.branch != branch:
+            raise VCSError('Branch option used on changeset not belonging '
+                           'to that branch')
+
+        def _next(changeset, branch):
+            try:
+                next_ = changeset.revision + 1
+                next_rev = changeset.repository.revisions[next_]
+            except IndexError:
+                raise ChangesetDoesNotExistError
+            cs = changeset.repository.get_changeset(next_rev)
+
+            if branch and branch != cs.branch:
+                return _next(cs, branch)
+
+            return cs
+
+        return _next(self, branch)
+
+    def prev(self, branch=None):
+        if branch and self.branch != branch:
+            raise VCSError('Branch option used on changeset not belonging '
+                           'to that branch')
+
+        def _prev(changeset, branch):
+            try:
+                prev_ = changeset.revision - 1
+                if prev_ < 0:
+                    raise IndexError
+                prev_rev = changeset.repository.revisions[prev_]
+            except IndexError:
+                raise ChangesetDoesNotExistError
+
+            cs = changeset.repository.get_changeset(prev_rev)
+
+            if branch and branch != cs.branch:
+                return _prev(cs, branch)
+
+            return cs
+
+        return _prev(self, branch)
+
+    def _fix_path(self, path):
+        """
+        Paths are stored without trailing slash so we need to get rid off it if
+        needed. Also mercurial keeps filenodes as str so we need to decode
+        from unicode to str
+        """
+        if path.endswith('/'):
+            path = path.rstrip('/')
+
+        return safe_str(path)
+
+    def _get_kind(self, path):
+        path = self._fix_path(path)
+        if path in self._file_paths:
+            return NodeKind.FILE
+        elif path in self._dir_paths:
+            return NodeKind.DIR
+        else:
+            raise ChangesetError("Node does not exist at the given path %r"
+                % (path))
+
+    def _get_filectx(self, path):
+        path = self._fix_path(path)
+        if self._get_kind(path) != NodeKind.FILE:
+            raise ChangesetError("File does not exist for revision %r at "
+                " %r" % (self.revision, path))
+        return self._ctx.filectx(path)
+
+    def get_file_mode(self, path):
+        """
+        Returns stat mode of the file at the given ``path``.
+        """
+        fctx = self._get_filectx(path)
+        if 'x' in fctx.flags():
+            return 0100755
+        else:
+            return 0100644
+
+    def get_file_content(self, path):
+        """
+        Returns content of the file at given ``path``.
+        """
+        fctx = self._get_filectx(path)
+        return fctx.data()
+
+    def get_file_size(self, path):
+        """
+        Returns size of the file at given ``path``.
+        """
+        fctx = self._get_filectx(path)
+        return fctx.size()
+
+    def get_file_changeset(self, path):
+        """
+        Returns last commit of the file at the given ``path``.
+        """
+        fctx = self._get_filectx(path)
+        changeset = self.repository.get_changeset(fctx.linkrev())
+        return changeset
+
+    def get_file_history(self, path):
+        """
+        Returns history of file as reversed list of ``Changeset`` objects for
+        which file at given ``path`` has been modified.
+        """
+        fctx = self._get_filectx(path)
+        nodes = [fctx.filectx(x).node() for x in fctx.filelog()]
+        changesets = [self.repository.get_changeset(hex(node))
+            for node in reversed(nodes)]
+        return changesets
+
+    def get_file_annotate(self, path):
+        """
+        Returns a list of three element tuples with lineno,changeset and line
+        """
+        fctx = self._get_filectx(path)
+        annotate = []
+        for i, annotate_data in enumerate(fctx.annotate()):
+            ln_no = i + 1
+            annotate.append((ln_no, self.repository\
+                             .get_changeset(hex(annotate_data[0].node())),
+                             annotate_data[1],))
+
+        return annotate
+
+    def fill_archive(self, stream=None, kind='tgz', prefix=None,
+                     subrepos=False):
+        """
+        Fills up given stream.
+
+        :param stream: file like object.
+        :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
+            Default: ``tgz``.
+        :param prefix: name of root directory in archive.
+            Default is repository name and changeset's raw_id joined with dash
+            (``repo-tip.<KIND>``).
+        :param subrepos: include subrepos in this archive.
+
+        :raise ImproperArchiveTypeError: If given kind is wrong.
+        :raise VcsError: If given stream is None
+        """
+
+        allowed_kinds = settings.ARCHIVE_SPECS.keys()
+        if kind not in allowed_kinds:
+            raise ImproperArchiveTypeError('Archive kind not supported use one'
+                'of %s', allowed_kinds)
+
+        if stream is None:
+            raise VCSError('You need to pass in a valid stream for filling'
+                           ' with archival data')
+
+        if prefix is None:
+            prefix = '%s-%s' % (self.repository.name, self.short_id)
+        elif prefix.startswith('/'):
+            raise VCSError("Prefix cannot start with leading slash")
+        elif prefix.strip() == '':
+            raise VCSError("Prefix cannot be empty")
+
+        archival.archive(self.repository._repo, stream, self.raw_id,
+                         kind, prefix=prefix, subrepos=subrepos)
+
+        #stream.close()
+
+        if stream.closed and hasattr(stream, 'name'):
+            stream = open(stream.name, 'rb')
+        elif hasattr(stream, 'mode') and 'r' not in stream.mode:
+            stream = open(stream.name, 'rb')
+        else:
+            stream.seek(0)
+
+    def get_nodes(self, path):
+        """
+        Returns combined ``DirNode`` and ``FileNode`` objects list representing
+        state of changeset at the given ``path``. If node at the given ``path``
+        is not instance of ``DirNode``, ChangesetError would be raised.
+        """
+
+        if self._get_kind(path) != NodeKind.DIR:
+            raise ChangesetError("Directory does not exist for revision %r at "
+                " %r" % (self.revision, path))
+        path = self._fix_path(path)
+        filenodes = [FileNode(f, changeset=self) for f in self._file_paths
+            if os.path.dirname(f) == path]
+        dirs = path == '' and '' or [d for d in self._dir_paths
+            if d and posixpath.dirname(d) == path]
+        dirnodes = [DirNode(d, changeset=self) for d in dirs
+            if os.path.dirname(d) == path]
+        nodes = dirnodes + filenodes
+        # cache nodes
+        for node in nodes:
+            self.nodes[node.path] = node
+        nodes.sort()
+        return nodes
+
+    def get_node(self, path):
+        """
+        Returns ``Node`` object from the given ``path``. If there is no node at
+        the given ``path``, ``ChangesetError`` would be raised.
+        """
+
+        path = self._fix_path(path)
+
+        if not path in self.nodes:
+            if path in self._file_paths:
+                node = FileNode(path, changeset=self)
+            elif path in self._dir_paths or path in self._dir_paths:
+                if path == '':
+                    node = RootNode(changeset=self)
+                else:
+                    node = DirNode(path, changeset=self)
+            else:
+                raise NodeDoesNotExistError("There is no file nor directory "
+                    "at the given path: %r at revision %r"
+                    % (path, self.short_id))
+            # cache node
+            self.nodes[path] = node
+        return self.nodes[path]
+
+    @LazyProperty
+    def affected_files(self):
+        """
+        Get's a fast accessible file changes for given changeset
+        """
+        return self._ctx.files()
+
+    @property
+    def added(self):
+        """
+        Returns list of added ``FileNode`` objects.
+        """
+        return AddedFileNodesGenerator([n for n in self.status[1]], self)
+
+    @property
+    def changed(self):
+        """
+        Returns list of modified ``FileNode`` objects.
+        """
+        return ChangedFileNodesGenerator([n for n in  self.status[0]], self)
+
+    @property
+    def removed(self):
+        """
+        Returns list of removed ``FileNode`` objects.
+        """
+        return RemovedFileNodesGenerator([n for n in self.status[2]], self)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/hg/inmemory.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,110 @@
+import datetime
+import errno
+
+from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset
+from rhodecode.lib.vcs.exceptions import RepositoryError
+
+from ...utils.hgcompat import memfilectx, memctx, hex
+
+
+class MercurialInMemoryChangeset(BaseInMemoryChangeset):
+
+    def commit(self, message, author, parents=None, branch=None, date=None,
+            **kwargs):
+        """
+        Performs in-memory commit (doesn't check workdir in any way) and
+        returns newly created ``Changeset``. Updates repository's
+        ``revisions``.
+
+        :param message: message of the commit
+        :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
+        :param parents: single parent or sequence of parents from which commit
+          would be derieved
+        :param date: ``datetime.datetime`` instance. Defaults to
+          ``datetime.datetime.now()``.
+        :param branch: branch name, as string. If none given, default backend's
+          branch would be used.
+
+        :raises ``CommitError``: if any error occurs while committing
+        """
+        self.check_integrity(parents)
+
+        from .repository import MercurialRepository
+        if not isinstance(message, str) or not isinstance(author, str):
+            raise RepositoryError('Given message and author needs to be '
+                                  'an <str> instance')
+
+        if branch is None:
+            branch = MercurialRepository.DEFAULT_BRANCH_NAME
+        kwargs['branch'] = branch
+
+        def filectxfn(_repo, memctx, path):
+            """
+            Marks given path as added/changed/removed in a given _repo. This is
+            for internal mercurial commit function.
+            """
+
+            # check if this path is removed
+            if path in (node.path for node in self.removed):
+                # Raising exception is a way to mark node for removal
+                raise IOError(errno.ENOENT, '%s is deleted' % path)
+
+            # check if this path is added
+            for node in self.added:
+                if node.path == path:
+                    return memfilectx(path=node.path,
+                        data=(node.content.encode('utf8')
+                              if not node.is_binary else node.content),
+                        islink=False,
+                        isexec=node.is_executable,
+                        copied=False)
+
+            # or changed
+            for node in self.changed:
+                if node.path == path:
+                    return memfilectx(path=node.path,
+                        data=(node.content.encode('utf8')
+                              if not node.is_binary else node.content),
+                        islink=False,
+                        isexec=node.is_executable,
+                        copied=False)
+
+            raise RepositoryError("Given path haven't been marked as added,"
+                "changed or removed (%s)" % path)
+
+        parents = [None, None]
+        for i, parent in enumerate(self.parents):
+            if parent is not None:
+                parents[i] = parent._ctx.node()
+
+        if date and isinstance(date, datetime.datetime):
+            date = date.ctime()
+
+        commit_ctx = memctx(repo=self.repository._repo,
+            parents=parents,
+            text='',
+            files=self.get_paths(),
+            filectxfn=filectxfn,
+            user=author,
+            date=date,
+            extra=kwargs)
+
+        # injecting given _repo params
+        commit_ctx._text = message
+        commit_ctx._user = author
+        commit_ctx._date = date
+
+        # TODO: Catch exceptions!
+        n = self.repository._repo.commitctx(commit_ctx)
+        # Returns mercurial node
+        self._commit_ctx = commit_ctx  # For reference
+        # Update vcs repository object & recreate mercurial _repo
+        # new_ctx = self.repository._repo[node]
+        # new_tip = self.repository.get_changeset(new_ctx.hex())
+        new_id = hex(n)
+        self.repository.revisions.append(new_id)
+        self._repo = self.repository._get_repo(create=False)
+        self.repository.branches = self.repository._get_branches()
+        tip = self.repository.get_changeset()
+        self.reset()
+        return tip
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/hg/repository.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,521 @@
+import os
+import time
+import datetime
+import urllib
+import urllib2
+
+from rhodecode.lib.vcs.backends.base import BaseRepository
+from .workdir import MercurialWorkdir
+from .changeset import MercurialChangeset
+from .inmemory import MercurialInMemoryChangeset
+
+from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError, \
+    ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError, \
+    VCSError, TagAlreadyExistError, TagDoesNotExistError
+from rhodecode.lib.vcs.utils import author_email, author_name, date_fromtimestamp, \
+    makedate, safe_unicode
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
+from rhodecode.lib.vcs.utils.paths import abspath
+
+from ...utils.hgcompat import ui, nullid, match, patch, diffopts, clone, \
+    get_contact, pull, localrepository, RepoLookupError, Abort, RepoError, hex
+
+
+class MercurialRepository(BaseRepository):
+    """
+    Mercurial repository backend
+    """
+    DEFAULT_BRANCH_NAME = 'default'
+    scm = 'hg'
+
+    def __init__(self, repo_path, create=False, baseui=None, src_url=None,
+                 update_after_clone=False):
+        """
+        Raises RepositoryError if repository could not be find at the given
+        ``repo_path``.
+
+        :param repo_path: local path of the repository
+        :param create=False: if set to True, would try to create repository if
+           it does not exist rather than raising exception
+        :param baseui=None: user data
+        :param src_url=None: would try to clone repository from given location
+        :param update_after_clone=False: sets update of working copy after
+          making a clone
+        """
+
+        if not isinstance(repo_path, str):
+            raise VCSError('Mercurial backend requires repository path to '
+                           'be instance of <str> got %s instead' %
+                           type(repo_path))
+
+        self.path = abspath(repo_path)
+        self.baseui = baseui or ui.ui()
+        # We've set path and ui, now we can set _repo itself
+        self._repo = self._get_repo(create, src_url, update_after_clone)
+
+    @property
+    def _empty(self):
+        """
+        Checks if repository is empty without any changesets
+        """
+        # TODO: Following raises errors when using InMemoryChangeset...
+        # return len(self._repo.changelog) == 0
+        return len(self.revisions) == 0
+
+    @LazyProperty
+    def revisions(self):
+        """
+        Returns list of revisions' ids, in ascending order.  Being lazy
+        attribute allows external tools to inject shas from cache.
+        """
+        return self._get_all_revisions()
+
+    @LazyProperty
+    def name(self):
+        return os.path.basename(self.path)
+
+    @LazyProperty
+    def branches(self):
+        return self._get_branches()
+
+    def _get_branches(self, closed=False):
+        """
+        Get's branches for this repository
+        Returns only not closed branches by default
+
+        :param closed: return also closed branches for mercurial
+        """
+
+        if self._empty:
+            return {}
+
+        def _branchtags(localrepo):
+            """
+            Patched version of mercurial branchtags to not return the closed
+            branches
+
+            :param localrepo: locarepository instance
+            """
+
+            bt = {}
+            bt_closed = {}
+            for bn, heads in localrepo.branchmap().iteritems():
+                tip = heads[-1]
+                if 'close' in localrepo.changelog.read(tip)[5]:
+                    bt_closed[bn] = tip
+                else:
+                    bt[bn] = tip
+
+            if closed:
+                bt.update(bt_closed)
+            return bt
+
+        sortkey = lambda ctx: ctx[0]  # sort by name
+        _branches = [(safe_unicode(n), hex(h),) for n, h in
+                     _branchtags(self._repo).items()]
+
+        return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
+
+    @LazyProperty
+    def tags(self):
+        """
+        Get's tags for this repository
+        """
+        return self._get_tags()
+
+    def _get_tags(self):
+        if self._empty:
+            return {}
+
+        sortkey = lambda ctx: ctx[0]  # sort by name
+        _tags = [(safe_unicode(n), hex(h),) for n, h in
+                 self._repo.tags().items()]
+
+        return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
+
+    def tag(self, name, user, revision=None, message=None, date=None,
+            **kwargs):
+        """
+        Creates and returns a tag for the given ``revision``.
+
+        :param name: name for new tag
+        :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
+        :param revision: changeset id for which new tag would be created
+        :param message: message of the tag's commit
+        :param date: date of tag's commit
+
+        :raises TagAlreadyExistError: if tag with same name already exists
+        """
+        if name in self.tags:
+            raise TagAlreadyExistError("Tag %s already exists" % name)
+        changeset = self.get_changeset(revision)
+        local = kwargs.setdefault('local', False)
+
+        if message is None:
+            message = "Added tag %s for changeset %s" % (name,
+                changeset.short_id)
+
+        if date is None:
+            date = datetime.datetime.now().ctime()
+
+        try:
+            self._repo.tag(name, changeset._ctx.node(), message, local, user,
+                date)
+        except Abort, e:
+            raise RepositoryError(e.message)
+
+        # Reinitialize tags
+        self.tags = self._get_tags()
+        tag_id = self.tags[name]
+
+        return self.get_changeset(revision=tag_id)
+
+    def remove_tag(self, name, user, message=None, date=None):
+        """
+        Removes tag with the given ``name``.
+
+        :param name: name of the tag to be removed
+        :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
+        :param message: message of the tag's removal commit
+        :param date: date of tag's removal commit
+
+        :raises TagDoesNotExistError: if tag with given name does not exists
+        """
+        if name not in self.tags:
+            raise TagDoesNotExistError("Tag %s does not exist" % name)
+        if message is None:
+            message = "Removed tag %s" % name
+        if date is None:
+            date = datetime.datetime.now().ctime()
+        local = False
+
+        try:
+            self._repo.tag(name, nullid, message, local, user, date)
+            self.tags = self._get_tags()
+        except Abort, e:
+            raise RepositoryError(e.message)
+
+    @LazyProperty
+    def bookmarks(self):
+        """
+        Get's bookmarks for this repository
+        """
+        return self._get_bookmarks()
+
+    def _get_bookmarks(self):
+        if self._empty:
+            return {}
+
+        sortkey = lambda ctx: ctx[0]  # sort by name
+        _bookmarks = [(safe_unicode(n), hex(h),) for n, h in
+                 self._repo._bookmarks.items()]
+        return OrderedDict(sorted(_bookmarks, key=sortkey, reverse=True))
+
+    def _get_all_revisions(self):
+
+        return map(lambda x: hex(x[7]), self._repo.changelog.index)[:-1]
+
+    def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
+                  context=3):
+        """
+        Returns (git like) *diff*, as plain text. Shows changes introduced by
+        ``rev2`` since ``rev1``.
+
+        :param rev1: Entry point from which diff is shown. Can be
+          ``self.EMPTY_CHANGESET`` - in this case, patch showing all
+          the changes since empty state of the repository until ``rev2``
+        :param rev2: Until which revision changes should be shown.
+        :param ignore_whitespace: If set to ``True``, would not show whitespace
+          changes. Defaults to ``False``.
+        :param context: How many lines before/after changed lines should be
+          shown. Defaults to ``3``.
+        """
+        # Check if given revisions are present at repository (may raise
+        # ChangesetDoesNotExistError)
+        if rev1 != self.EMPTY_CHANGESET:
+            self.get_changeset(rev1)
+        self.get_changeset(rev2)
+
+        file_filter = match(self.path, '', [path])
+        return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
+                          opts=diffopts(git=True,
+                                        ignorews=ignore_whitespace,
+                                        context=context)))
+
+    def _check_url(self, url):
+        """
+        Function will check given url and try to verify if it's a valid
+        link. Sometimes it may happened that mercurial will issue basic
+        auth request that can cause whole API to hang when used from python
+        or other external calls.
+
+        On failures it'll raise urllib2.HTTPError, return code 200 if url
+        is valid or True if it's a local path
+        """
+
+        from mercurial.util import url as Url
+
+        # those authnadlers are patched for python 2.6.5 bug an
+        # infinit looping when given invalid resources
+        from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
+
+        # check first if it's not an local url
+        if os.path.isdir(url) or url.startswith('file:'):
+            return True
+
+        handlers = []
+        test_uri, authinfo = Url(url).authinfo()
+
+        if authinfo:
+            #create a password manager
+            passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
+            passmgr.add_password(*authinfo)
+
+            handlers.extend((httpbasicauthhandler(passmgr),
+                             httpdigestauthhandler(passmgr)))
+
+        o = urllib2.build_opener(*handlers)
+        o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
+                        ('Accept', 'application/mercurial-0.1')]
+
+        q = {"cmd": 'between'}
+        q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
+        qs = '?%s' % urllib.urlencode(q)
+        cu = "%s%s" % (test_uri, qs)
+        req = urllib2.Request(cu, None, {})
+
+        try:
+            resp = o.open(req)
+            return resp.code == 200
+        except Exception, e:
+            # means it cannot be cloned
+            raise urllib2.URLError(e)
+
+    def _get_repo(self, create, src_url=None, update_after_clone=False):
+        """
+        Function will check for mercurial repository in given path and return
+        a localrepo object. If there is no repository in that path it will
+        raise an exception unless ``create`` parameter is set to True - in
+        that case repository would be created and returned.
+        If ``src_url`` is given, would try to clone repository from the
+        location at given clone_point. Additionally it'll make update to
+        working copy accordingly to ``update_after_clone`` flag
+        """
+        try:
+            if src_url:
+                url = str(self._get_url(src_url))
+                opts = {}
+                if not update_after_clone:
+                    opts.update({'noupdate': True})
+                try:
+                    self._check_url(url)
+                    clone(self.baseui, url, self.path, **opts)
+#                except urllib2.URLError:
+#                    raise Abort("Got HTTP 404 error")
+                except Exception:
+                    raise
+                # Don't try to create if we've already cloned repo
+                create = False
+            return localrepository(self.baseui, self.path, create=create)
+        except (Abort, RepoError), err:
+            if create:
+                msg = "Cannot create repository at %s. Original error was %s"\
+                    % (self.path, err)
+            else:
+                msg = "Not valid repository at %s. Original error was %s"\
+                    % (self.path, err)
+            raise RepositoryError(msg)
+
+    @LazyProperty
+    def in_memory_changeset(self):
+        return MercurialInMemoryChangeset(self)
+
+    @LazyProperty
+    def description(self):
+        undefined_description = u'unknown'
+        return safe_unicode(self._repo.ui.config('web', 'description',
+                                   undefined_description, untrusted=True))
+
+    @LazyProperty
+    def contact(self):
+        undefined_contact = u'Unknown'
+        return safe_unicode(get_contact(self._repo.ui.config)
+                            or undefined_contact)
+
+    @LazyProperty
+    def last_change(self):
+        """
+        Returns last change made on this repository as datetime object
+        """
+        return date_fromtimestamp(self._get_mtime(), makedate()[1])
+
+    def _get_mtime(self):
+        try:
+            return time.mktime(self.get_changeset().date.timetuple())
+        except RepositoryError:
+            #fallback to filesystem
+            cl_path = os.path.join(self.path, '.hg', "00changelog.i")
+            st_path = os.path.join(self.path, '.hg', "store")
+            if os.path.exists(cl_path):
+                return os.stat(cl_path).st_mtime
+            else:
+                return os.stat(st_path).st_mtime
+
+    def _get_hidden(self):
+        return self._repo.ui.configbool("web", "hidden", untrusted=True)
+
+    def _get_revision(self, revision):
+        """
+        Get's an ID revision given as str. This will always return a fill
+        40 char revision number
+
+        :param revision: str or int or None
+        """
+
+        if self._empty:
+            raise EmptyRepositoryError("There are no changesets yet")
+
+        if revision in [-1, 'tip', None]:
+            revision = 'tip'
+
+        try:
+            revision = hex(self._repo.lookup(revision))
+        except (IndexError, ValueError, RepoLookupError, TypeError):
+            raise ChangesetDoesNotExistError("Revision %r does not "
+                                    "exist for this repository %s" \
+                                    % (revision, self))
+        return revision
+
+    def _get_archives(self, archive_name='tip'):
+        allowed = self.baseui.configlist("web", "allow_archive",
+                                         untrusted=True)
+        for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
+            if i[0] in allowed or self._repo.ui.configbool("web",
+                                                           "allow" + i[0],
+                                                           untrusted=True):
+                yield {"type": i[0], "extension": i[1], "node": archive_name}
+
+    def _get_url(self, url):
+        """
+        Returns normalized url. If schema is not given, would fall
+        to filesystem
+        (``file:///``) schema.
+        """
+        url = str(url)
+        if url != 'default' and not '://' in url:
+            url = "file:" + urllib.pathname2url(url)
+        return url
+
+    def get_changeset(self, revision=None):
+        """
+        Returns ``MercurialChangeset`` object representing repository's
+        changeset at the given ``revision``.
+        """
+        revision = self._get_revision(revision)
+        changeset = MercurialChangeset(repository=self, revision=revision)
+        return changeset
+
+    def get_changesets(self, start=None, end=None, start_date=None,
+                       end_date=None, branch_name=None, reverse=False):
+        """
+        Returns iterator of ``MercurialChangeset`` objects from start to end
+        (both are inclusive)
+
+        :param start: None, str, int or mercurial lookup format
+        :param end:  None, str, int or mercurial lookup format
+        :param start_date:
+        :param end_date:
+        :param branch_name:
+        :param reversed: return changesets in reversed order
+        """
+
+        start_raw_id = self._get_revision(start)
+        start_pos = self.revisions.index(start_raw_id) if start else None
+        end_raw_id = self._get_revision(end)
+        end_pos = self.revisions.index(end_raw_id) if end else None
+
+        if None not in [start, end] and start_pos > end_pos:
+            raise RepositoryError("start revision '%s' cannot be "
+                                  "after end revision '%s'" % (start, end))
+
+        if branch_name and branch_name not in self.branches.keys():
+            raise BranchDoesNotExistError('Such branch %s does not exists for'
+                                  ' this repository' % branch_name)
+        if end_pos is not None:
+            end_pos += 1
+
+        slice_ = reversed(self.revisions[start_pos:end_pos]) if reverse else \
+            self.revisions[start_pos:end_pos]
+
+        for id_ in slice_:
+            cs = self.get_changeset(id_)
+            if branch_name and cs.branch != branch_name:
+                continue
+            if start_date and cs.date < start_date:
+                continue
+            if end_date and cs.date > end_date:
+                continue
+
+            yield cs
+
+    def pull(self, url):
+        """
+        Tries to pull changes from external location.
+        """
+        url = self._get_url(url)
+        try:
+            pull(self.baseui, self._repo, url)
+        except Abort, err:
+            # Propagate error but with vcs's type
+            raise RepositoryError(str(err))
+
+    @LazyProperty
+    def workdir(self):
+        """
+        Returns ``Workdir`` instance for this repository.
+        """
+        return MercurialWorkdir(self)
+
+    def get_config_value(self, section, name, config_file=None):
+        """
+        Returns configuration value for a given [``section``] and ``name``.
+
+        :param section: Section we want to retrieve value from
+        :param name: Name of configuration we want to retrieve
+        :param config_file: A path to file which should be used to retrieve
+          configuration from (might also be a list of file paths)
+        """
+        if config_file is None:
+            config_file = []
+        elif isinstance(config_file, basestring):
+            config_file = [config_file]
+
+        config = self._repo.ui
+        for path in config_file:
+            config.readconfig(path)
+        return config.config(section, name)
+
+    def get_user_name(self, config_file=None):
+        """
+        Returns user's name from global configuration file.
+
+        :param config_file: A path to file which should be used to retrieve
+          configuration from (might also be a list of file paths)
+        """
+        username = self.get_config_value('ui', 'username')
+        if username:
+            return author_name(username)
+        return None
+
+    def get_user_email(self, config_file=None):
+        """
+        Returns user's email from global configuration file.
+
+        :param config_file: A path to file which should be used to retrieve
+          configuration from (might also be a list of file paths)
+        """
+        username = self.get_config_value('ui', 'username')
+        if username:
+            return author_email(username)
+        return None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/backends/hg/workdir.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,21 @@
+from rhodecode.lib.vcs.backends.base import BaseWorkdir
+from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
+
+from ...utils.hgcompat import hg_merge
+
+
+class MercurialWorkdir(BaseWorkdir):
+
+    def get_branch(self):
+        return self.repository._repo.dirstate.branch()
+
+    def get_changeset(self):
+        return self.repository.get_changeset()
+
+    def checkout_branch(self, branch=None):
+        if branch is None:
+            branch = self.repository.DEFAULT_BRANCH_NAME
+        if branch not in self.repository.branches:
+            raise BranchDoesNotExistError
+
+        hg_merge.update(self.repository._repo, branch, False, False, None)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/conf/settings.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,33 @@
+import os
+import tempfile
+from rhodecode.lib.vcs.utils.paths import get_user_home
+
+abspath = lambda * p: os.path.abspath(os.path.join(*p))
+
+VCSRC_PATH = os.environ.get('VCSRC_PATH')
+
+if not VCSRC_PATH:
+    HOME_ = get_user_home()
+    if not HOME_:
+        HOME_ = tempfile.gettempdir()
+
+VCSRC_PATH = VCSRC_PATH or abspath(HOME_, '.vcsrc')
+if os.path.isdir(VCSRC_PATH):
+    VCSRC_PATH = os.path.join(VCSRC_PATH, '__init__.py')
+
+BACKENDS = {
+    'hg': 'vcs.backends.hg.MercurialRepository',
+    'git': 'vcs.backends.git.GitRepository',
+}
+
+ARCHIVE_SPECS = {
+    'tar': ('application/x-tar', '.tar'),
+    'tbz2': ('application/x-bzip2', '.tar.bz2'),
+    'tgz': ('application/x-gzip', '.tar.gz'),
+    'zip': ('application/zip', '.zip'),
+}
+
+BACKENDS = {
+    'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
+    'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/exceptions.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+"""
+    vcs.exceptions
+    ~~~~~~~~~~~~~~
+
+    Custom exceptions module
+
+    :created_on: Apr 8, 2010
+    :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
+"""
+
+
+class VCSError(Exception):
+    pass
+
+
+class RepositoryError(VCSError):
+    pass
+
+
+class EmptyRepositoryError(RepositoryError):
+    pass
+
+
+class TagAlreadyExistError(RepositoryError):
+    pass
+
+
+class TagDoesNotExistError(RepositoryError):
+    pass
+
+
+class BranchAlreadyExistError(RepositoryError):
+    pass
+
+
+class BranchDoesNotExistError(RepositoryError):
+    pass
+
+
+class ChangesetError(RepositoryError):
+    pass
+
+
+class ChangesetDoesNotExistError(ChangesetError):
+    pass
+
+
+class CommitError(RepositoryError):
+    pass
+
+
+class NothingChangedError(CommitError):
+    pass
+
+
+class NodeError(VCSError):
+    pass
+
+
+class RemovedFileNodeError(NodeError):
+    pass
+
+
+class NodeAlreadyExistsError(CommitError):
+    pass
+
+
+class NodeAlreadyChangedError(CommitError):
+    pass
+
+
+class NodeDoesNotExistError(CommitError):
+    pass
+
+
+class NodeNotChangedError(CommitError):
+    pass
+
+
+class NodeAlreadyAddedError(CommitError):
+    pass
+
+
+class NodeAlreadyRemovedError(CommitError):
+    pass
+
+
+class ImproperArchiveTypeError(VCSError):
+    pass
+
+class CommandError(VCSError):
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/nodes.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,551 @@
+# -*- coding: utf-8 -*-
+"""
+    vcs.nodes
+    ~~~~~~~~~
+
+    Module holding everything related to vcs nodes.
+
+    :created_on: Apr 8, 2010
+    :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
+"""
+import stat
+import posixpath
+import mimetypes
+
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.utils import safe_unicode
+from rhodecode.lib.vcs.exceptions import NodeError
+from rhodecode.lib.vcs.exceptions import RemovedFileNodeError
+
+from pygments import lexers
+
+
+class NodeKind:
+    DIR = 1
+    FILE = 2
+
+
+class NodeState:
+    ADDED = u'added'
+    CHANGED = u'changed'
+    NOT_CHANGED = u'not changed'
+    REMOVED = u'removed'
+
+
+class NodeGeneratorBase(object):
+    """
+    Base class for removed added and changed filenodes, it's a lazy generator
+    class that will create filenodes only on iteration or call
+
+    The len method doesn't need to create filenodes at all
+    """
+
+    def __init__(self, current_paths, cs):
+        self.cs = cs
+        self.current_paths = current_paths
+
+    def __call__(self):
+        return [n for n in self]
+
+    def __getslice__(self, i, j):
+        for p in self.current_paths[i:j]:
+            yield self.cs.get_node(p)
+
+    def __len__(self):
+        return len(self.current_paths)
+
+    def __iter__(self):
+        for p in self.current_paths:
+            yield self.cs.get_node(p)
+
+
+class AddedFileNodesGenerator(NodeGeneratorBase):
+    """
+    Class holding Added files for current changeset
+    """
+    pass
+
+
+class ChangedFileNodesGenerator(NodeGeneratorBase):
+    """
+    Class holding Changed files for current changeset
+    """
+    pass
+
+
+class RemovedFileNodesGenerator(NodeGeneratorBase):
+    """
+    Class holding removed files for current changeset
+    """
+    def __iter__(self):
+        for p in self.current_paths:
+            yield RemovedFileNode(path=p)
+
+    def __getslice__(self, i, j):
+        for p in self.current_paths[i:j]:
+            yield RemovedFileNode(path=p)
+
+
+class Node(object):
+    """
+    Simplest class representing file or directory on repository.  SCM backends
+    should use ``FileNode`` and ``DirNode`` subclasses rather than ``Node``
+    directly.
+
+    Node's ``path`` cannot start with slash as we operate on *relative* paths
+    only. Moreover, every single node is identified by the ``path`` attribute,
+    so it cannot end with slash, too. Otherwise, path could lead to mistakes.
+    """
+
+    def __init__(self, path, kind):
+        if path.startswith('/'):
+            raise NodeError("Cannot initialize Node objects with slash at "
+                "the beginning as only relative paths are supported")
+        self.path = path.rstrip('/')
+        if path == '' and kind != NodeKind.DIR:
+            raise NodeError("Only DirNode and its subclasses may be "
+                            "initialized with empty path")
+        self.kind = kind
+        #self.dirs, self.files = [], []
+        if self.is_root() and not self.is_dir():
+            raise NodeError("Root node cannot be FILE kind")
+
+    @LazyProperty
+    def parent(self):
+        parent_path = self.get_parent_path()
+        if parent_path:
+            if self.changeset:
+                return self.changeset.get_node(parent_path)
+            return DirNode(parent_path)
+        return None
+
+    @LazyProperty
+    def name(self):
+        """
+        Returns name of the node so if its path
+        then only last part is returned.
+        """
+        return safe_unicode(self.path.rstrip('/').split('/')[-1])
+
+    def _get_kind(self):
+        return self._kind
+
+    def _set_kind(self, kind):
+        if hasattr(self, '_kind'):
+            raise NodeError("Cannot change node's kind")
+        else:
+            self._kind = kind
+            # Post setter check (path's trailing slash)
+            if self.path.endswith('/'):
+                raise NodeError("Node's path cannot end with slash")
+
+    kind = property(_get_kind, _set_kind)
+
+    def __cmp__(self, other):
+        """
+        Comparator using name of the node, needed for quick list sorting.
+        """
+        kind_cmp = cmp(self.kind, other.kind)
+        if kind_cmp:
+            return kind_cmp
+        return cmp(self.name, other.name)
+
+    def __eq__(self, other):
+        for attr in ['name', 'path', 'kind']:
+            if getattr(self, attr) != getattr(other, attr):
+                return False
+        if self.is_file():
+            if self.content != other.content:
+                return False
+        else:
+            # For DirNode's check without entering each dir
+            self_nodes_paths = list(sorted(n.path for n in self.nodes))
+            other_nodes_paths = list(sorted(n.path for n in self.nodes))
+            if self_nodes_paths != other_nodes_paths:
+                return False
+        return True
+
+    def __nq__(self, other):
+        return not self.__eq__(other)
+
+    def __repr__(self):
+        return '<%s %r>' % (self.__class__.__name__, self.path)
+
+    def __str__(self):
+        return self.__repr__()
+
+    def __unicode__(self):
+        return self.name
+
+    def get_parent_path(self):
+        """
+        Returns node's parent path or empty string if node is root.
+        """
+        if self.is_root():
+            return ''
+        return posixpath.dirname(self.path.rstrip('/')) + '/'
+
+    def is_file(self):
+        """
+        Returns ``True`` if node's kind is ``NodeKind.FILE``, ``False``
+        otherwise.
+        """
+        return self.kind == NodeKind.FILE
+
+    def is_dir(self):
+        """
+        Returns ``True`` if node's kind is ``NodeKind.DIR``, ``False``
+        otherwise.
+        """
+        return self.kind == NodeKind.DIR
+
+    def is_root(self):
+        """
+        Returns ``True`` if node is a root node and ``False`` otherwise.
+        """
+        return self.kind == NodeKind.DIR and self.path == ''
+
+    @LazyProperty
+    def added(self):
+        return self.state is NodeState.ADDED
+
+    @LazyProperty
+    def changed(self):
+        return self.state is NodeState.CHANGED
+
+    @LazyProperty
+    def not_changed(self):
+        return self.state is NodeState.NOT_CHANGED
+
+    @LazyProperty
+    def removed(self):
+        return self.state is NodeState.REMOVED
+
+
+class FileNode(Node):
+    """
+    Class representing file nodes.
+
+    :attribute: path: path to the node, relative to repostiory's root
+    :attribute: content: if given arbitrary sets content of the file
+    :attribute: changeset: if given, first time content is accessed, callback
+    :attribute: mode: octal stat mode for a node. Default is 0100644.
+    """
+
+    def __init__(self, path, content=None, changeset=None, mode=None):
+        """
+        Only one of ``content`` and ``changeset`` may be given. Passing both
+        would raise ``NodeError`` exception.
+
+        :param path: relative path to the node
+        :param content: content may be passed to constructor
+        :param changeset: if given, will use it to lazily fetch content
+        :param mode: octal representation of ST_MODE (i.e. 0100644)
+        """
+
+        if content and changeset:
+            raise NodeError("Cannot use both content and changeset")
+        super(FileNode, self).__init__(path, kind=NodeKind.FILE)
+        self.changeset = changeset
+        self._content = content
+        self._mode = mode or 0100644
+
+    @LazyProperty
+    def mode(self):
+        """
+        Returns lazily mode of the FileNode. If ``changeset`` is not set, would
+        use value given at initialization or 0100644 (default).
+        """
+        if self.changeset:
+            mode = self.changeset.get_file_mode(self.path)
+        else:
+            mode = self._mode
+        return mode
+
+    @property
+    def content(self):
+        """
+        Returns lazily content of the FileNode. If possible, would try to
+        decode content from UTF-8.
+        """
+        if self.changeset:
+            content = self.changeset.get_file_content(self.path)
+        else:
+            content = self._content
+
+        if bool(content and '\0' in content):
+            return content
+        return safe_unicode(content)
+
+    @LazyProperty
+    def size(self):
+        if self.changeset:
+            return self.changeset.get_file_size(self.path)
+        raise NodeError("Cannot retrieve size of the file without related "
+            "changeset attribute")
+
+    @LazyProperty
+    def message(self):
+        if self.changeset:
+            return self.last_changeset.message
+        raise NodeError("Cannot retrieve message of the file without related "
+            "changeset attribute")
+
+    @LazyProperty
+    def last_changeset(self):
+        if self.changeset:
+            return self.changeset.get_file_changeset(self.path)
+        raise NodeError("Cannot retrieve last changeset of the file without "
+            "related changeset attribute")
+
+    def get_mimetype(self):
+        """
+        Mimetype is calculated based on the file's content. If ``_mimetype``
+        attribute is available, it will be returned (backends which store
+        mimetypes or can easily recognize them, should set this private
+        attribute to indicate that type should *NOT* be calculated).
+        """
+        if hasattr(self, '_mimetype'):
+            if (isinstance(self._mimetype,(tuple,list,)) and
+                len(self._mimetype) == 2):
+                return self._mimetype
+            else:
+                raise NodeError('given _mimetype attribute must be an 2 '
+                               'element list or tuple')
+
+        mtype,encoding = mimetypes.guess_type(self.name)
+
+        if mtype is None:
+            if self.is_binary:
+                mtype = 'application/octet-stream'
+                encoding = None
+            else:
+                mtype = 'text/plain'
+                encoding = None
+        return mtype,encoding
+
+    @LazyProperty
+    def mimetype(self):
+        """
+        Wrapper around full mimetype info. It returns only type of fetched
+        mimetype without the encoding part. use get_mimetype function to fetch
+        full set of (type,encoding)
+        """
+        return self.get_mimetype()[0]
+
+    @LazyProperty
+    def mimetype_main(self):
+        return self.mimetype.split('/')[0]
+
+    @LazyProperty
+    def lexer(self):
+        """
+        Returns pygment's lexer class. Would try to guess lexer taking file's
+        content, name and mimetype.
+        """
+        try:
+            lexer = lexers.guess_lexer_for_filename(self.name, self.content)
+        except lexers.ClassNotFound:
+            lexer = lexers.TextLexer()
+        # returns first alias
+        return lexer
+
+    @LazyProperty
+    def lexer_alias(self):
+        """
+        Returns first alias of the lexer guessed for this file.
+        """
+        return self.lexer.aliases[0]
+
+    @LazyProperty
+    def history(self):
+        """
+        Returns a list of changeset for this file in which the file was changed
+        """
+        if self.changeset is None:
+            raise NodeError('Unable to get changeset for this FileNode')
+        return self.changeset.get_file_history(self.path)
+
+    @LazyProperty
+    def annotate(self):
+        """
+        Returns a list of three element tuples with lineno,changeset and line
+        """
+        if self.changeset is None:
+            raise NodeError('Unable to get changeset for this FileNode')
+        return self.changeset.get_file_annotate(self.path)
+
+    @LazyProperty
+    def state(self):
+        if not self.changeset:
+            raise NodeError("Cannot check state of the node if it's not "
+                "linked with changeset")
+        elif self.path in (node.path for node in self.changeset.added):
+            return NodeState.ADDED
+        elif self.path in (node.path for node in self.changeset.changed):
+            return NodeState.CHANGED
+        else:
+            return NodeState.NOT_CHANGED
+
+    @property
+    def is_binary(self):
+        """
+        Returns True if file has binary content.
+        """
+        bin = '\0' in self.content
+        return bin
+
+    @LazyProperty
+    def extension(self):
+        """Returns filenode extension"""
+        return self.name.split('.')[-1]
+
+    def is_executable(self):
+        """
+        Returns ``True`` if file has executable flag turned on.
+        """
+        return bool(self.mode & stat.S_IXUSR)
+
+
+class RemovedFileNode(FileNode):
+    """
+    Dummy FileNode class - trying to access any public attribute except path,
+    name, kind or state (or methods/attributes checking those two) would raise
+    RemovedFileNodeError.
+    """
+    ALLOWED_ATTRIBUTES = ['name', 'path', 'state', 'is_root', 'is_file',
+        'is_dir', 'kind', 'added', 'changed', 'not_changed', 'removed']
+
+    def __init__(self, path):
+        """
+        :param path: relative path to the node
+        """
+        super(RemovedFileNode, self).__init__(path=path)
+
+    def __getattribute__(self, attr):
+        if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
+            return super(RemovedFileNode, self).__getattribute__(attr)
+        raise RemovedFileNodeError("Cannot access attribute %s on "
+            "RemovedFileNode" % attr)
+
+    @LazyProperty
+    def state(self):
+        return NodeState.REMOVED
+
+
+class DirNode(Node):
+    """
+    DirNode stores list of files and directories within this node.
+    Nodes may be used standalone but within repository context they
+    lazily fetch data within same repositorty's changeset.
+    """
+
+    def __init__(self, path, nodes=(), changeset=None):
+        """
+        Only one of ``nodes`` and ``changeset`` may be given. Passing both
+        would raise ``NodeError`` exception.
+
+        :param path: relative path to the node
+        :param nodes: content may be passed to constructor
+        :param changeset: if given, will use it to lazily fetch content
+        :param size: always 0 for ``DirNode``
+        """
+        if nodes and changeset:
+            raise NodeError("Cannot use both nodes and changeset")
+        super(DirNode, self).__init__(path, NodeKind.DIR)
+        self.changeset = changeset
+        self._nodes = nodes
+
+    @LazyProperty
+    def content(self):
+        raise NodeError("%s represents a dir and has no ``content`` attribute"
+            % self)
+
+    @LazyProperty
+    def nodes(self):
+        if self.changeset:
+            nodes = self.changeset.get_nodes(self.path)
+        else:
+            nodes = self._nodes
+        self._nodes_dict = dict((node.path, node) for node in nodes)
+        return sorted(nodes)
+
+    @LazyProperty
+    def files(self):
+        return sorted((node for node in self.nodes if node.is_file()))
+
+    @LazyProperty
+    def dirs(self):
+        return sorted((node for node in self.nodes if node.is_dir()))
+
+    def __iter__(self):
+        for node in self.nodes:
+            yield node
+
+    def get_node(self, path):
+        """
+        Returns node from within this particular ``DirNode``, so it is now
+        allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
+        'docs'. In order to access deeper nodes one must fetch nodes between
+        them first - this would work::
+
+           docs = root.get_node('docs')
+           docs.get_node('api').get_node('index.rst')
+
+        :param: path - relative to the current node
+
+        .. note::
+           To access lazily (as in example above) node have to be initialized
+           with related changeset object - without it node is out of
+           context and may know nothing about anything else than nearest
+           (located at same level) nodes.
+        """
+        try:
+            path = path.rstrip('/')
+            if path == '':
+                raise NodeError("Cannot retrieve node without path")
+            self.nodes  # access nodes first in order to set _nodes_dict
+            paths = path.split('/')
+            if len(paths) == 1:
+                if not self.is_root():
+                    path = '/'.join((self.path, paths[0]))
+                else:
+                    path = paths[0]
+                return self._nodes_dict[path]
+            elif len(paths) > 1:
+                if self.changeset is None:
+                    raise NodeError("Cannot access deeper "
+                                    "nodes without changeset")
+                else:
+                    path1, path2 = paths[0], '/'.join(paths[1:])
+                    return self.get_node(path1).get_node(path2)
+            else:
+                raise KeyError
+        except KeyError:
+            raise NodeError("Node does not exist at %s" % path)
+
+    @LazyProperty
+    def state(self):
+        raise NodeError("Cannot access state of DirNode")
+
+    @LazyProperty
+    def size(self):
+        size = 0
+        for root, dirs, files in self.changeset.walk(self.path):
+            for f in files:
+                size += f.size
+
+        return size
+
+
+class RootNode(DirNode):
+    """
+    DirNode being the root node of the repository.
+    """
+
+    def __init__(self, nodes=(), changeset=None):
+        super(RootNode, self).__init__(path='', nodes=nodes,
+            changeset=changeset)
+
+    def __repr__(self):
+        return '<%s>' % self.__class__.__name__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,139 @@
+"""
+This module provides some useful tools for ``vcs`` like annotate/diff html
+output. It also includes some internal helpers.
+"""
+import sys
+import time
+import datetime
+
+
+def makedate():
+    lt = time.localtime()
+    if lt[8] == 1 and time.daylight:
+        tz = time.altzone
+    else:
+        tz = time.timezone
+    return time.mktime(lt), tz
+
+
+def date_fromtimestamp(unixts, tzoffset=0):
+    """
+    Makes a local datetime object out of unix timestamp
+
+    :param unixts:
+    :param tzoffset:
+    """
+
+    return datetime.datetime.fromtimestamp(float(unixts))
+
+
+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 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 author_email(author):
+    """
+    returns email address of given author.
+    If any of <,> sign are found, it fallbacks to regex findall()
+    and returns first found result or empty string
+
+    Regex taken from http://www.regular-expressions.info/email.html
+    """
+    import re
+    r = author.find('>')
+    l = author.find('<')
+
+    if l == -1 or r == -1:
+        # fallback to regex match of email out of a string
+        email_re = re.compile(r"""[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!"""
+                              r"""#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z"""
+                              r"""0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]"""
+                              r"""*[a-z0-9])?""", re.IGNORECASE)
+        m = re.findall(email_re, author)
+        return m[0] if m else ''
+
+    return author[l + 1:r].strip()
+
+
+def author_name(author):
+    """
+    get name of author, or else username.
+    It'll try to find an email in the author string and just cut it off
+    to get the username
+    """
+
+    if not '@' in author:
+        return author
+    else:
+        return author.replace(author_email(author), '').replace('<', '')\
+            .replace('>', '').strip()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/annotate.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,177 @@
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.nodes import FileNode
+from pygments.formatters import HtmlFormatter
+from pygments import highlight
+
+import StringIO
+
+
+def annotate_highlight(filenode, annotate_from_changeset_func=None,
+        order=None, headers=None, **options):
+    """
+    Returns html portion containing annotated table with 3 columns: line
+    numbers, changeset information and pygmentized line of code.
+
+    :param filenode: FileNode object
+    :param annotate_from_changeset_func: function taking changeset and
+      returning single annotate cell; needs break line at the end
+    :param order: ordered sequence of ``ls`` (line numbers column),
+      ``annotate`` (annotate column), ``code`` (code column); Default is
+      ``['ls', 'annotate', 'code']``
+    :param headers: dictionary with headers (keys are whats in ``order``
+      parameter)
+    """
+    options['linenos'] = True
+    formatter = AnnotateHtmlFormatter(filenode=filenode, order=order,
+        headers=headers,
+        annotate_from_changeset_func=annotate_from_changeset_func, **options)
+    lexer = filenode.lexer
+    highlighted = highlight(filenode.content, lexer, formatter)
+    return highlighted
+
+
+class AnnotateHtmlFormatter(HtmlFormatter):
+
+    def __init__(self, filenode, annotate_from_changeset_func=None,
+            order=None, **options):
+        """
+        If ``annotate_from_changeset_func`` is passed it should be a function
+        which returns string from the given changeset. For example, we may pass
+        following function as ``annotate_from_changeset_func``::
+
+            def changeset_to_anchor(changeset):
+                return '<a href="/changesets/%s/">%s</a>\n' %\
+                       (changeset.id, changeset.id)
+
+        :param annotate_from_changeset_func: see above
+        :param order: (default: ``['ls', 'annotate', 'code']``); order of
+          columns;
+        :param options: standard pygment's HtmlFormatter options, there is
+          extra option tough, ``headers``. For instance we can pass::
+
+             formatter = AnnotateHtmlFormatter(filenode, headers={
+                'ls': '#',
+                'annotate': 'Annotate',
+                'code': 'Code',
+             })
+
+        """
+        super(AnnotateHtmlFormatter, self).__init__(**options)
+        self.annotate_from_changeset_func = annotate_from_changeset_func
+        self.order = order or ('ls', 'annotate', 'code')
+        headers = options.pop('headers', None)
+        if headers and not ('ls' in headers and 'annotate' in headers and
+            'code' in headers):
+            raise ValueError("If headers option dict is specified it must "
+                "all 'ls', 'annotate' and 'code' keys")
+        self.headers = headers
+        if isinstance(filenode, FileNode):
+            self.filenode = filenode
+        else:
+            raise VCSError("This formatter expect FileNode parameter, not %r"
+                % type(filenode))
+
+    def annotate_from_changeset(self, changeset):
+        """
+        Returns full html line for single changeset per annotated line.
+        """
+        if self.annotate_from_changeset_func:
+            return self.annotate_from_changeset_func(changeset)
+        else:
+            return ''.join((changeset.id, '\n'))
+
+    def _wrap_tablelinenos(self, inner):
+        dummyoutfile = StringIO.StringIO()
+        lncount = 0
+        for t, line in inner:
+            if t:
+                lncount += 1
+            dummyoutfile.write(line)
+
+        fl = self.linenostart
+        mw = len(str(lncount + fl - 1))
+        sp = self.linenospecial
+        st = self.linenostep
+        la = self.lineanchors
+        aln = self.anchorlinenos
+        if sp:
+            lines = []
+
+            for i in range(fl, fl + lncount):
+                if i % st == 0:
+                    if i % sp == 0:
+                        if aln:
+                            lines.append('<a href="#%s-%d" class="special">'
+                                         '%*d</a>' %
+                                         (la, i, mw, i))
+                        else:
+                            lines.append('<span class="special">'
+                                         '%*d</span>' % (mw, i))
+                    else:
+                        if aln:
+                            lines.append('<a href="#%s-%d">'
+                                         '%*d</a>' % (la, i, mw, i))
+                        else:
+                            lines.append('%*d' % (mw, i))
+                else:
+                    lines.append('')
+            ls = '\n'.join(lines)
+        else:
+            lines = []
+            for i in range(fl, fl + lncount):
+                if i % st == 0:
+                    if aln:
+                        lines.append('<a href="#%s-%d">%*d</a>' \
+                                     % (la, i, mw, i))
+                    else:
+                        lines.append('%*d' % (mw, i))
+                else:
+                    lines.append('')
+            ls = '\n'.join(lines)
+
+        annotate_changesets = [tup[1] for tup in self.filenode.annotate]
+        # If pygments cropped last lines break we need do that too
+        ln_cs = len(annotate_changesets)
+        ln_ = len(ls.splitlines())
+        if  ln_cs > ln_:
+            annotate_changesets = annotate_changesets[:ln_ - ln_cs]
+        annotate = ''.join((self.annotate_from_changeset(changeset)
+            for changeset in annotate_changesets))
+        # in case you wonder about the seemingly redundant <div> here:
+        # since the content in the other cell also is wrapped in a div,
+        # some browsers in some configurations seem to mess up the formatting.
+        '''
+        yield 0, ('<table class="%stable">' % self.cssclass +
+                  '<tr><td class="linenos"><div class="linenodiv"><pre>' +
+                  ls + '</pre></div></td>' +
+                  '<td class="code">')
+        yield 0, dummyoutfile.getvalue()
+        yield 0, '</td></tr></table>'
+
+        '''
+        headers_row = []
+        if self.headers:
+            headers_row = ['<tr class="annotate-header">']
+            for key in self.order:
+                td = ''.join(('<td>', self.headers[key], '</td>'))
+                headers_row.append(td)
+            headers_row.append('</tr>')
+
+        body_row_start = ['<tr>']
+        for key in self.order:
+            if key == 'ls':
+                body_row_start.append(
+                    '<td class="linenos"><div class="linenodiv"><pre>' +
+                    ls + '</pre></div></td>')
+            elif key == 'annotate':
+                body_row_start.append(
+                    '<td class="annotate"><div class="annotatediv"><pre>' +
+                    annotate + '</pre></div></td>')
+            elif key == 'code':
+                body_row_start.append('<td class="code">')
+        yield 0, ('<table class="%stable">' % self.cssclass +
+                  ''.join(headers_row) +
+                  ''.join(body_row_start)
+                  )
+        yield 0, dummyoutfile.getvalue()
+        yield 0, '</td></tr></table>'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/archivers.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+"""
+    vcs.utils.archivers
+    ~~~~~~~~~~~~~~~~~~~
+
+    set of archiver functions for creating archives from repository content
+
+    :created_on: Jan 21, 2011
+    :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
+"""
+
+
+class BaseArchiver(object):
+
+    def __init__(self):
+        self.archive_file = self._get_archive_file()
+
+    def addfile(self):
+        """
+        Adds a file to archive container
+        """
+        pass
+
+    def close(self):
+        """
+        Closes and finalizes operation of archive container object
+        """
+        self.archive_file.close()
+
+    def _get_archive_file(self):
+        """
+        Returns container for specific archive
+        """
+        raise NotImplementedError()
+
+
+class TarArchiver(BaseArchiver):
+    pass
+
+
+class Tbz2Archiver(BaseArchiver):
+    pass
+
+
+class TgzArchiver(BaseArchiver):
+    pass
+
+
+class ZipArchiver(BaseArchiver):
+    pass
+
+
+def get_archiver(self, kind):
+    """
+    Returns instance of archiver class specific to given kind
+
+    :param kind: archive kind
+    """
+
+    archivers = {
+        'tar': TarArchiver,
+        'tbz2': Tbz2Archiver,
+        'tgz': TgzArchiver,
+        'zip': ZipArchiver,
+    }
+
+    return archivers[kind]()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/baseui_config.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,47 @@
+from mercurial import ui, config
+
+
+def make_ui(self, path='hgwebdir.config'):
+    """
+    A funcion that will read python rc files and make an ui from read options
+
+    :param path: path to mercurial config file
+    """
+    #propagated from mercurial documentation
+    sections = [
+                'alias',
+                'auth',
+                'decode/encode',
+                'defaults',
+                'diff',
+                'email',
+                'extensions',
+                'format',
+                'merge-patterns',
+                'merge-tools',
+                'hooks',
+                'http_proxy',
+                'smtp',
+                'patch',
+                'paths',
+                'profiling',
+                'server',
+                'trusted',
+                'ui',
+                'web',
+                ]
+
+    repos = path
+    baseui = ui.ui()
+    cfg = config.config()
+    cfg.read(repos)
+    self.paths = cfg.items('paths')
+    self.base_path = self.paths[0][1].replace('*', '')
+    self.check_repo_dir(self.paths)
+    self.set_statics(cfg)
+
+    for section in sections:
+        for k, v in cfg.items(section):
+            baseui.setconfig(section, k, v)
+
+    return baseui
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/compat.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,13 @@
+"""
+Various utilities to work with Python < 2.7.
+
+Those utilities may be deleted once ``vcs`` stops support for older Python
+versions.
+"""
+import sys
+
+
+if sys.version_info >= (2, 7):
+    unittest = __import__('unittest')
+else:
+    unittest = __import__('unittest2')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/diffs.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,460 @@
+# -*- coding: utf-8 -*-
+# original copyright: 2007-2008 by Armin Ronacher
+# licensed under the BSD license.
+
+import re
+import difflib
+import logging
+
+from difflib import unified_diff
+from itertools import tee, imap
+
+from mercurial.match import match
+
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.nodes import FileNode, NodeError
+
+
+def get_udiff(filenode_old, filenode_new,show_whitespace=True):
+    """
+    Returns unified diff between given ``filenode_old`` and ``filenode_new``.
+    """
+    try:
+        filenode_old_date = filenode_old.last_changeset.date
+    except NodeError:
+        filenode_old_date = None
+
+    try:
+        filenode_new_date = filenode_new.last_changeset.date
+    except NodeError:
+        filenode_new_date = None
+
+    for filenode in (filenode_old, filenode_new):
+        if not isinstance(filenode, FileNode):
+            raise VCSError("Given object should be FileNode object, not %s"
+                % filenode.__class__)
+
+    if filenode_old_date and filenode_new_date:
+        if not filenode_old_date < filenode_new_date:
+            logging.debug("Generating udiff for filenodes with not increasing "
+                "dates")
+
+    vcs_udiff = unified_diff(filenode_old.content.splitlines(True),
+                               filenode_new.content.splitlines(True),
+                               filenode_old.name,
+                               filenode_new.name,
+                               filenode_old_date,
+                               filenode_old_date)
+    return vcs_udiff
+
+
+def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True):
+    """
+    Returns git style diff between given ``filenode_old`` and ``filenode_new``.
+
+    :param ignore_whitespace: ignore whitespaces in diff
+    """
+
+    for filenode in (filenode_old, filenode_new):
+        if not isinstance(filenode, FileNode):
+            raise VCSError("Given object should be FileNode object, not %s"
+                % filenode.__class__)
+
+    old_raw_id = getattr(filenode_old.changeset, 'raw_id', '0' * 40)
+    new_raw_id = getattr(filenode_new.changeset, 'raw_id', '0' * 40)
+
+    repo = filenode_new.changeset.repository
+    vcs_gitdiff = repo._get_diff(old_raw_id, new_raw_id, filenode_new.path,
+                                 ignore_whitespace)
+
+    return vcs_gitdiff
+
+
+class DiffProcessor(object):
+    """
+    Give it a unified diff and it returns a list of the files that were
+    mentioned in the diff together with a dict of meta information that
+    can be used to render it in a HTML template.
+    """
+    _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
+
+    def __init__(self, diff, differ='diff', format='udiff'):
+        """
+        :param diff:   a text in diff format or generator
+        :param format: format of diff passed, `udiff` or `gitdiff`
+        """
+        if isinstance(diff, basestring):
+            diff = [diff]
+
+        self.__udiff = diff
+        self.__format = format
+        self.adds = 0
+        self.removes = 0
+
+        if isinstance(self.__udiff, basestring):
+            self.lines = iter(self.__udiff.splitlines(1))
+
+        elif self.__format == 'gitdiff':
+            udiff_copy = self.copy_iterator()
+            self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
+        else:
+            udiff_copy = self.copy_iterator()
+            self.lines = imap(self.escaper, udiff_copy)
+
+        # Select a differ.
+        if differ == 'difflib':
+            self.differ = self._highlight_line_difflib
+        else:
+            self.differ = self._highlight_line_udiff
+
+    def escaper(self, string):
+        return string.replace('<', '&lt;').replace('>', '&gt;')
+
+    def copy_iterator(self):
+        """
+        make a fresh copy of generator, we should not iterate thru
+        an original as it's needed for repeating operations on
+        this instance of DiffProcessor
+        """
+        self.__udiff, iterator_copy = tee(self.__udiff)
+        return iterator_copy
+
+    def _extract_rev(self, line1, line2):
+        """
+        Extract the filename and revision hint from a line.
+        """
+
+        try:
+            if line1.startswith('--- ') and line2.startswith('+++ '):
+                l1 = line1[4:].split(None, 1)
+                old_filename = l1[0].lstrip('a/') if len(l1) >= 1 else None
+                old_rev = l1[1] if len(l1) == 2 else 'old'
+
+                l2 = line2[4:].split(None, 1)
+                new_filename = l2[0].lstrip('b/') if len(l1) >= 1 else None
+                new_rev = l2[1] if len(l2) == 2 else 'new'
+
+                filename = old_filename if (old_filename !=
+                                            'dev/null') else new_filename
+
+                return filename, new_rev, old_rev
+        except (ValueError, IndexError):
+            pass
+
+        return None, None, None
+
+    def _parse_gitdiff(self, diffiterator):
+        def line_decoder(l):
+            if l.startswith('+') and not l.startswith('+++'):
+                self.adds += 1
+            elif l.startswith('-') and not l.startswith('---'):
+                self.removes += 1
+            return l.decode('utf8', 'replace')
+
+        output = list(diffiterator)
+        size = len(output)
+
+        if size == 2:
+            l = []
+            l.extend([output[0]])
+            l.extend(output[1].splitlines(1))
+            return map(line_decoder, l)
+        elif size == 1:
+            return  map(line_decoder, output[0].splitlines(1))
+        elif size == 0:
+            return []
+
+        raise Exception('wrong size of diff %s' % size)
+
+    def _highlight_line_difflib(self, line, next):
+        """
+        Highlight inline changes in both lines.
+        """
+
+        if line['action'] == 'del':
+            old, new = line, next
+        else:
+            old, new = next, line
+
+        oldwords = re.split(r'(\W)', old['line'])
+        newwords = re.split(r'(\W)', new['line'])
+
+        sequence = difflib.SequenceMatcher(None, oldwords, newwords)
+
+        oldfragments, newfragments = [], []
+        for tag, i1, i2, j1, j2 in sequence.get_opcodes():
+            oldfrag = ''.join(oldwords[i1:i2])
+            newfrag = ''.join(newwords[j1:j2])
+            if tag != 'equal':
+                if oldfrag:
+                    oldfrag = '<del>%s</del>' % oldfrag
+                if newfrag:
+                    newfrag = '<ins>%s</ins>' % newfrag
+            oldfragments.append(oldfrag)
+            newfragments.append(newfrag)
+
+        old['line'] = "".join(oldfragments)
+        new['line'] = "".join(newfragments)
+
+    def _highlight_line_udiff(self, line, next):
+        """
+        Highlight inline changes in both lines.
+        """
+        start = 0
+        limit = min(len(line['line']), len(next['line']))
+        while start < limit and line['line'][start] == next['line'][start]:
+            start += 1
+        end = -1
+        limit -= start
+        while -end <= limit and line['line'][end] == next['line'][end]:
+            end -= 1
+        end += 1
+        if start or end:
+            def do(l):
+                last = end + len(l['line'])
+                if l['action'] == 'add':
+                    tag = 'ins'
+                else:
+                    tag = 'del'
+                l['line'] = '%s<%s>%s</%s>%s' % (
+                    l['line'][:start],
+                    tag,
+                    l['line'][start:last],
+                    tag,
+                    l['line'][last:]
+                )
+            do(line)
+            do(next)
+
+    def _parse_udiff(self):
+        """
+        Parse the diff an return data for the template.
+        """
+        lineiter = self.lines
+        files = []
+        try:
+            line = lineiter.next()
+            # skip first context
+            skipfirst = True
+            while 1:
+                # continue until we found the old file
+                if not line.startswith('--- '):
+                    line = lineiter.next()
+                    continue
+
+                chunks = []
+                filename, old_rev, new_rev = \
+                    self._extract_rev(line, lineiter.next())
+                files.append({
+                    'filename':         filename,
+                    'old_revision':     old_rev,
+                    'new_revision':     new_rev,
+                    'chunks':           chunks
+                })
+
+                line = lineiter.next()
+                while line:
+                    match = self._chunk_re.match(line)
+                    if not match:
+                        break
+
+                    lines = []
+                    chunks.append(lines)
+
+                    old_line, old_end, new_line, new_end = \
+                        [int(x or 1) for x in match.groups()[:-1]]
+                    old_line -= 1
+                    new_line -= 1
+                    context = len(match.groups()) == 5
+                    old_end += old_line
+                    new_end += new_line
+
+                    if context:
+                        if not skipfirst:
+                            lines.append({
+                                'old_lineno': '...',
+                                'new_lineno': '...',
+                                'action': 'context',
+                                'line': line,
+                            })
+                        else:
+                            skipfirst = False
+
+                    line = lineiter.next()
+                    while old_line < old_end or new_line < new_end:
+                        if line:
+                            command, line = line[0], line[1:]
+                        else:
+                            command = ' '
+                        affects_old = affects_new = False
+
+                        # ignore those if we don't expect them
+                        if command in '#@':
+                            continue
+                        elif command == '+':
+                            affects_new = True
+                            action = 'add'
+                        elif command == '-':
+                            affects_old = True
+                            action = 'del'
+                        else:
+                            affects_old = affects_new = True
+                            action = 'unmod'
+
+                        old_line += affects_old
+                        new_line += affects_new
+                        lines.append({
+                            'old_lineno':   affects_old and old_line or '',
+                            'new_lineno':   affects_new and new_line or '',
+                            'action':       action,
+                            'line':         line
+                        })
+                        line = lineiter.next()
+
+        except StopIteration:
+            pass
+
+        # highlight inline changes
+        for file in files:
+            for chunk in chunks:
+                lineiter = iter(chunk)
+                #first = True
+                try:
+                    while 1:
+                        line = lineiter.next()
+                        if line['action'] != 'unmod':
+                            nextline = lineiter.next()
+                            if nextline['action'] == 'unmod' or \
+                               nextline['action'] == line['action']:
+                                continue
+                            self.differ(line, nextline)
+                except StopIteration:
+                    pass
+
+        return files
+
+    def prepare(self):
+        """
+        Prepare the passed udiff for HTML rendering. It'l return a list
+        of dicts
+        """
+        return self._parse_udiff()
+
+    def _safe_id(self, idstring):
+        """Make a string safe for including in an id attribute.
+
+        The HTML spec says that id attributes 'must begin with
+        a letter ([A-Za-z]) and may be followed by any number
+        of letters, digits ([0-9]), hyphens ("-"), underscores
+        ("_"), colons (":"), and periods (".")'. These regexps
+        are slightly over-zealous, in that they remove colons
+        and periods unnecessarily.
+
+        Whitespace is transformed into underscores, and then
+        anything which is not a hyphen or a character that
+        matches \w (alphanumerics and underscore) is removed.
+
+        """
+        # Transform all whitespace to underscore
+        idstring = re.sub(r'\s', "_", '%s' % idstring)
+        # Remove everything that is not a hyphen or a member of \w
+        idstring = re.sub(r'(?!-)\W', "", idstring).lower()
+        return idstring
+
+    def raw_diff(self):
+        """
+        Returns raw string as udiff
+        """
+        udiff_copy = self.copy_iterator()
+        if self.__format == 'gitdiff':
+            udiff_copy = self._parse_gitdiff(udiff_copy)
+        return u''.join(udiff_copy)
+
+    def as_html(self, table_class='code-difftable', line_class='line',
+                new_lineno_class='lineno old', old_lineno_class='lineno new',
+                code_class='code'):
+        """
+        Return udiff as html table with customized css classes
+        """
+        def _link_to_if(condition, label, url):
+            """
+            Generates a link if condition is meet or just the label if not.
+            """
+
+            if condition:
+                return '''<a href="%(url)s">%(label)s</a>''' % {'url': url,
+                                                                'label': label}
+            else:
+                return label
+        diff_lines = self.prepare()
+        _html_empty = True
+        _html = []
+        _html.append('''<table class="%(table_class)s">\n''' \
+                                            % {'table_class': table_class})
+        for diff in diff_lines:
+            for line in diff['chunks']:
+                _html_empty = False
+                for change in line:
+                    _html.append('''<tr class="%(line_class)s %(action)s">\n''' \
+                        % {'line_class': line_class,
+                           'action': change['action']})
+                    anchor_old_id = ''
+                    anchor_new_id = ''
+                    anchor_old = "%(filename)s_o%(oldline_no)s" % \
+                                {'filename': self._safe_id(diff['filename']),
+                                 'oldline_no': change['old_lineno']}
+                    anchor_new = "%(filename)s_n%(oldline_no)s" % \
+                                {'filename': self._safe_id(diff['filename']),
+                                 'oldline_no': change['new_lineno']}
+                    cond_old = change['old_lineno'] != '...' and \
+                                                        change['old_lineno']
+                    cond_new = change['new_lineno'] != '...' and \
+                                                        change['new_lineno']
+                    if cond_old:
+                        anchor_old_id = 'id="%s"' % anchor_old
+                    if cond_new:
+                        anchor_new_id = 'id="%s"' % anchor_new
+                    ###########################################################
+                    # OLD LINE NUMBER
+                    ###########################################################
+                    _html.append('''\t<td %(a_id)s class="%(old_lineno_cls)s">''' \
+                                    % {'a_id': anchor_old_id,
+                                       'old_lineno_cls': old_lineno_class})
+
+                    _html.append('''<pre>%(link)s</pre>''' \
+                        % {'link':
+                        _link_to_if(cond_old, change['old_lineno'], '#%s' \
+                                                                % anchor_old)})
+                    _html.append('''</td>\n''')
+                    ###########################################################
+                    # NEW LINE NUMBER
+                    ###########################################################
+
+                    _html.append('''\t<td %(a_id)s class="%(new_lineno_cls)s">''' \
+                                    % {'a_id': anchor_new_id,
+                                       'new_lineno_cls': new_lineno_class})
+
+                    _html.append('''<pre>%(link)s</pre>''' \
+                        % {'link':
+                        _link_to_if(cond_new, change['new_lineno'], '#%s' \
+                                                                % anchor_new)})
+                    _html.append('''</td>\n''')
+                    ###########################################################
+                    # CODE
+                    ###########################################################
+                    _html.append('''\t<td class="%(code_class)s">''' \
+                                                % {'code_class': code_class})
+                    _html.append('''\n\t\t<pre>%(code)s</pre>\n''' \
+                                                % {'code': change['line']})
+                    _html.append('''\t</td>''')
+                    _html.append('''\n</tr>\n''')
+        _html.append('''</table>''')
+        if _html_empty:
+            return None
+        return ''.join(_html)
+
+    def stat(self):
+        """
+        Returns tuple of adde,and removed lines for this instance
+        """
+        return self.adds, self.removes
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/fakemod.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,13 @@
+import imp
+
+
+def create_module(name, path):
+    """
+    Returns module created *on the fly*. Returned module would have name same
+    as given ``name`` and would contain code read from file at the given
+    ``path`` (it may also be a zip or package containing *__main__* module).
+    """
+    module = imp.new_module(name)
+    module.__file__ = path
+    execfile(path, module.__dict__)
+    return module
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/filesize.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,28 @@
+def filesizeformat(bytes, sep=' '):
+    """
+    Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
+    102 B, 2.3 GB etc).
+
+    Grabbed from Django (http://www.djangoproject.com), slightly modified.
+
+    :param bytes: size in bytes (as integer)
+    :param sep: string separator between number and abbreviation
+    """
+    try:
+        bytes = float(bytes)
+    except (TypeError, ValueError, UnicodeDecodeError):
+        return '0%sB' % sep
+
+    if bytes < 1024:
+        size = bytes
+        template = '%.0f%sB'
+    elif bytes < 1024 * 1024:
+        size = bytes / 1024
+        template = '%.0f%sKB'
+    elif bytes < 1024 * 1024 * 1024:
+        size = bytes / 1024 / 1024
+        template = '%.1f%sMB'
+    else:
+        size = bytes / 1024 / 1024 / 1024
+        template = '%.2f%sGB'
+    return template % (size, sep)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/helpers.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,252 @@
+"""
+Utitlites aimed to help achieve mostly basic tasks.
+"""
+from __future__ import division
+
+import re
+import time
+import datetime
+import os.path
+from subprocess import Popen, PIPE
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.exceptions import RepositoryError
+from rhodecode.lib.vcs.utils.paths import abspath
+
+ALIASES = ['hg', 'git']
+
+
+def get_scm(path, search_recursively=False, explicit_alias=None):
+    """
+    Returns one of alias from ``ALIASES`` (in order of precedence same as
+    shortcuts given in ``ALIASES``) and top working dir path for the given
+    argument. If no scm-specific directory is found or more than one scm is
+    found at that directory, ``VCSError`` is raised.
+
+    :param search_recursively: if set to ``True``, this function would try to
+      move up to parent directory every time no scm is recognized for the
+      currently checked path. Default: ``False``.
+    :param explicit_alias: can be one of available backend aliases, when given
+      it will return given explicit alias in repositories under more than one
+      version control, if explicit_alias is different than found it will raise
+      VCSError
+    """
+    if not os.path.isdir(path):
+        raise VCSError("Given path %s is not a directory" % path)
+
+    def get_scms(path):
+        return [(scm, path) for scm in get_scms_for_path(path)]
+
+    found_scms = get_scms(path)
+    while  not found_scms and search_recursively:
+        newpath = abspath(path, '..')
+        if newpath == path:
+            break
+        path = newpath
+        found_scms = get_scms(path)
+
+    if len(found_scms) > 1:
+        for scm in found_scms:
+            if scm[0] == explicit_alias:
+                return scm
+        raise VCSError('More than one [%s] scm found at given path %s'
+                       % (','.join((x[0] for x in found_scms)), path))
+
+    if len(found_scms) is 0:
+        raise VCSError('No scm found at given path %s' % path)
+
+    return found_scms[0]
+
+
+def get_scms_for_path(path):
+    """
+    Returns all scm's found at the given path. If no scm is recognized
+    - empty list is returned.
+
+    :param path: path to directory which should be checked. May be callable.
+
+    :raises VCSError: if given ``path`` is not a directory
+    """
+    from rhodecode.lib.vcs.backends import get_backend
+    if hasattr(path, '__call__'):
+        path = path()
+    if not os.path.isdir(path):
+        raise VCSError("Given path %r is not a directory" % path)
+
+    result = []
+    for key in ALIASES:
+        dirname = os.path.join(path, '.' + key)
+        if os.path.isdir(dirname):
+            result.append(key)
+            continue
+        # We still need to check if it's not bare repository as
+        # bare repos don't have working directories
+        try:
+            get_backend(key)(path)
+            result.append(key)
+            continue
+        except RepositoryError:
+            # Wrong backend
+            pass
+        except VCSError:
+            # No backend at all
+            pass
+    return result
+
+
+def get_repo_paths(path):
+    """
+    Returns path's subdirectories which seems to be a repository.
+    """
+    repo_paths = []
+    dirnames = (os.path.abspath(dirname) for dirname in os.listdir(path))
+    for dirname in dirnames:
+        try:
+            get_scm(dirname)
+            repo_paths.append(dirname)
+        except VCSError:
+            pass
+    return repo_paths
+
+
+def run_command(cmd, *args):
+    """
+    Runs command on the system with given ``args``.
+    """
+    command = ' '.join((cmd, args))
+    p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
+    stdout, stderr = p.communicate()
+    return p.retcode, stdout, stderr
+
+
+def get_highlighted_code(name, code, type='terminal'):
+    """
+    If pygments are available on the system
+    then returned output is colored. Otherwise
+    unchanged content is returned.
+    """
+    import logging
+    try:
+        import pygments
+        pygments
+    except ImportError:
+        return code
+    from pygments import highlight
+    from pygments.lexers import guess_lexer_for_filename, ClassNotFound
+    from pygments.formatters import TerminalFormatter
+
+    try:
+        lexer = guess_lexer_for_filename(name, code)
+        formatter = TerminalFormatter()
+        content = highlight(code, lexer, formatter)
+    except ClassNotFound:
+        logging.debug("Couldn't guess Lexer, will not use pygments.")
+        content = code
+    return content
+
+def parse_changesets(text):
+    """
+    Returns dictionary with *start*, *main* and *end* ids.
+
+    Examples::
+
+        >>> parse_changesets('aaabbb')
+        {'start': None, 'main': 'aaabbb', 'end': None}
+        >>> parse_changesets('aaabbb..cccddd')
+        {'start': 'aaabbb', 'main': None, 'end': 'cccddd'}
+
+    """
+    text = text.strip()
+    CID_RE = r'[a-zA-Z0-9]+'
+    if not '..' in text:
+        m = re.match(r'^(?P<cid>%s)$' % CID_RE, text)
+        if m:
+            return {
+                'start': None,
+                'main': text,
+                'end': None,
+            }
+    else:
+        RE = r'^(?P<start>%s)?\.{2,3}(?P<end>%s)?$' % (CID_RE, CID_RE)
+        m = re.match(RE, text)
+        if m:
+            result = m.groupdict()
+            result['main'] = None
+            return result
+    raise ValueError("IDs not recognized")
+
+def parse_datetime(text):
+    """
+    Parses given text and returns ``datetime.datetime`` instance or raises
+    ``ValueError``.
+
+    :param text: string of desired date/datetime or something more verbose,
+      like *yesterday*, *2weeks 3days*, etc.
+    """
+
+    text = text.strip().lower()
+
+    INPUT_FORMATS = (
+        '%Y-%m-%d %H:%M:%S',
+        '%Y-%m-%d %H:%M',
+        '%Y-%m-%d',
+        '%m/%d/%Y %H:%M:%S',
+        '%m/%d/%Y %H:%M',
+        '%m/%d/%Y',
+        '%m/%d/%y %H:%M:%S',
+        '%m/%d/%y %H:%M',
+        '%m/%d/%y',
+    )
+    for format in INPUT_FORMATS:
+        try:
+            return datetime.datetime(*time.strptime(text, format)[:6])
+        except ValueError:
+            pass
+
+    # Try descriptive texts
+    if text == 'tomorrow':
+        future = datetime.datetime.now() + datetime.timedelta(days=1)
+        args = future.timetuple()[:3] + (23, 59, 59)
+        return datetime.datetime(*args)
+    elif text == 'today':
+        return datetime.datetime(*datetime.datetime.today().timetuple()[:3])
+    elif text == 'now':
+        return datetime.datetime.now()
+    elif text == 'yesterday':
+        past = datetime.datetime.now() - datetime.timedelta(days=1)
+        return datetime.datetime(*past.timetuple()[:3])
+    else:
+        days = 0
+        matched = re.match(
+            r'^((?P<weeks>\d+) ?w(eeks?)?)? ?((?P<days>\d+) ?d(ays?)?)?$', text)
+        if matched:
+            groupdict = matched.groupdict()
+            if groupdict['days']:
+                days += int(matched.groupdict()['days'])
+            if groupdict['weeks']:
+                days += int(matched.groupdict()['weeks']) * 7
+            past = datetime.datetime.now() - datetime.timedelta(days=days)
+            return datetime.datetime(*past.timetuple()[:3])
+
+    raise ValueError('Wrong date: "%s"' % text)
+
+
+def get_dict_for_attrs(obj, attrs):
+    """
+    Returns dictionary for each attribute from given ``obj``.
+    """
+    data = {}
+    for attr in attrs:
+        data[attr] = getattr(obj, attr)
+    return data
+
+
+def get_total_seconds(timedelta):
+    """
+    Backported for Python 2.5.
+
+    See http://docs.python.org/library/datetime.html.
+    """
+    return ((timedelta.microseconds + (
+            timedelta.seconds +
+            timedelta.days * 24 * 60 * 60
+        ) * 10**6) / 10**6)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/hgcompat.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,12 @@
+"""Mercurial libs compatibility
+
+"""
+from mercurial import archival, merge as hg_merge, patch, ui
+from mercurial.commands import clone, nullid, pull
+from mercurial.context import memctx, memfilectx
+from mercurial.error import RepoError, RepoLookupError, Abort
+from mercurial.hgweb.common import get_contact
+from mercurial.localrepo import localrepository
+from mercurial.match import match
+from mercurial.mdiff import diffopts
+from mercurial.node import hex
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/imports.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,27 @@
+from rhodecode.lib.vcs.exceptions import VCSError
+
+
+def import_class(class_path):
+    """
+    Returns class from the given path.
+
+    For example, in order to get class located at
+    ``vcs.backends.hg.MercurialRepository``:
+
+        try:
+            hgrepo = import_class('vcs.backends.hg.MercurialRepository')
+        except VCSError:
+            # hadle error
+    """
+    splitted = class_path.split('.')
+    mod_path = '.'.join(splitted[:-1])
+    class_name = splitted[-1]
+    try:
+        class_mod = __import__(mod_path, {}, {}, [class_name])
+    except ImportError, err:
+        msg = "There was problem while trying to import backend class. "\
+            "Original error was:\n%s" % err
+        raise VCSError(msg)
+    cls = getattr(class_mod, class_name)
+
+    return cls
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/lazy.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,27 @@
+class LazyProperty(object):
+    """
+    Decorator for easier creation of ``property`` from potentially expensive to
+    calculate attribute of the class.
+
+    Usage::
+
+      class Foo(object):
+          @LazyProperty
+          def bar(self):
+              print 'Calculating self._bar'
+              return 42
+
+    Taken from http://blog.pythonisito.com/2008/08/lazy-descriptors.html and
+    used widely.
+    """
+
+    def __init__(self, func):
+        self._func = func
+        self.__name__ = func.__name__
+        self.__doc__ = func.__doc__
+
+    def __get__(self, obj, klass=None):
+        if obj is None:
+            return None
+        result = obj.__dict__[self.__name__] = self._func(obj)
+        return result
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/lockfiles.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,72 @@
+import os
+
+
+class LockFile(object):
+	"""Provides methods to obtain, check for, and release a file based lock which
+	should be used to handle concurrent access to the same file.
+
+	As we are a utility class to be derived from, we only use protected methods.
+
+	Locks will automatically be released on destruction"""
+	__slots__ = ("_file_path", "_owns_lock")
+
+	def __init__(self, file_path):
+		self._file_path = file_path
+		self._owns_lock = False
+
+	def __del__(self):
+		self._release_lock()
+
+	def _lock_file_path(self):
+		""":return: Path to lockfile"""
+		return "%s.lock" % (self._file_path)
+
+	def _has_lock(self):
+		""":return: True if we have a lock and if the lockfile still exists
+		:raise AssertionError: if our lock-file does not exist"""
+		if not self._owns_lock:
+			return False
+
+		return True
+
+	def _obtain_lock_or_raise(self):
+		"""Create a lock file as flag for other instances, mark our instance as lock-holder
+
+		:raise IOError: if a lock was already present or a lock file could not be written"""
+		if self._has_lock():
+			return
+		lock_file = self._lock_file_path()
+		if os.path.isfile(lock_file):
+			raise IOError("Lock for file %r did already exist, delete %r in case the lock is illegal" % (self._file_path, lock_file))
+
+		try:
+			fd = os.open(lock_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0)
+			os.close(fd)
+		except OSError,e:
+			raise IOError(str(e))
+
+		self._owns_lock = True
+
+	def _obtain_lock(self):
+		"""The default implementation will raise if a lock cannot be obtained.
+		Subclasses may override this method to provide a different implementation"""
+		return self._obtain_lock_or_raise()
+
+	def _release_lock(self):
+		"""Release our lock if we have one"""
+		if not self._has_lock():
+			return
+
+		# if someone removed our file beforhand, lets just flag this issue
+		# instead of failing, to make it more usable.
+		lfp = self._lock_file_path()
+		try:
+			# on bloody windows, the file needs write permissions to be removable.
+			# Why ...
+			if os.name == 'nt':
+				os.chmod(lfp, 0777)
+			# END handle win32
+			os.remove(lfp)
+		except OSError:
+			pass
+		self._owns_lock = False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/ordered_dict.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,102 @@
+"""Ordered dict implementation"""
+from UserDict import DictMixin
+
+
+class OrderedDict(dict, DictMixin):
+
+    def __init__(self, *args, **kwds):
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__end
+        except AttributeError:
+            self.clear()
+        self.update(*args, **kwds)
+
+    def clear(self):
+        self.__end = end = []
+        end += [None, end, end]         # sentinel node for doubly linked list
+        self.__map = {}                 # key --> [key, prev, next]
+        dict.clear(self)
+
+    def __setitem__(self, key, value):
+        if key not in self:
+            end = self.__end
+            curr = end[1]
+            curr[2] = end[1] = self.__map[key] = [key, curr, end]
+        dict.__setitem__(self, key, value)
+
+    def __delitem__(self, key):
+        dict.__delitem__(self, key)
+        key, prev, next = self.__map.pop(key)
+        prev[2] = next
+        next[1] = prev
+
+    def __iter__(self):
+        end = self.__end
+        curr = end[2]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[2]
+
+    def __reversed__(self):
+        end = self.__end
+        curr = end[1]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[1]
+
+    def popitem(self, last=True):
+        if not self:
+            raise KeyError('dictionary is empty')
+        if last:
+            key = reversed(self).next()
+        else:
+            key = iter(self).next()
+        value = self.pop(key)
+        return key, value
+
+    def __reduce__(self):
+        items = [[k, self[k]] for k in self]
+        tmp = self.__map, self.__end
+        del self.__map, self.__end
+        inst_dict = vars(self).copy()
+        self.__map, self.__end = tmp
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def keys(self):
+        return list(self)
+
+    setdefault = DictMixin.setdefault
+    update = DictMixin.update
+    pop = DictMixin.pop
+    values = DictMixin.values
+    items = DictMixin.items
+    iterkeys = DictMixin.iterkeys
+    itervalues = DictMixin.itervalues
+    iteritems = DictMixin.iteritems
+
+    def __repr__(self):
+        if not self:
+            return '%s()' % (self.__class__.__name__,)
+        return '%s(%r)' % (self.__class__.__name__, self.items())
+
+    def copy(self):
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        if isinstance(other, OrderedDict):
+            return len(self) == len(other) and self.items() == other.items()
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/paths.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,36 @@
+import os
+
+abspath = lambda * p: os.path.abspath(os.path.join(*p))
+
+
+def get_dirs_for_path(*paths):
+    """
+    Returns list of directories, including intermediate.
+    """
+    for path in paths:
+        head = path
+        while head:
+            head, tail = os.path.split(head)
+            if head:
+                yield head
+            else:
+                # We don't need to yield empty path
+                break
+
+
+def get_dir_size(path):
+    root_path = path
+    size = 0
+    for path, dirs, files in os.walk(root_path):
+        for f in files:
+            try:
+                size += os.path.getsize(os.path.join(path, f))
+            except OSError:
+                pass
+    return size
+
+def get_user_home():
+    """
+    Returns home path of the user.
+    """
+    return os.getenv('HOME', os.getenv('USERPROFILE'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/progressbar.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,419 @@
+# encoding: UTF-8
+import sys
+import datetime
+from string import Template
+from rhodecode.lib.vcs.utils.filesize import filesizeformat
+from rhodecode.lib.vcs.utils.helpers import get_total_seconds
+
+
+class ProgressBarError(Exception):
+    pass
+
+class AlreadyFinishedError(ProgressBarError):
+    pass
+
+
+class ProgressBar(object):
+
+    default_elements = ['percentage', 'bar', 'steps']
+
+    def __init__(self, steps=100, stream=None, elements=None):
+        self.step = 0
+        self.steps = steps
+        self.stream = stream or sys.stderr
+        self.bar_char = '='
+        self.width = 50
+        self.separator = ' | '
+        self.elements = elements or self.default_elements
+        self.started = None
+        self.finished = False
+        self.steps_label = 'Step'
+        self.time_label = 'Time'
+        self.eta_label = 'ETA'
+        self.speed_label = 'Speed'
+        self.transfer_label = 'Transfer'
+
+    def __str__(self):
+        return self.get_line()
+
+    def __iter__(self):
+        start = self.step
+        end = self.steps + 1
+        for x in xrange(start, end):
+            self.render(x)
+            yield x
+
+    def get_separator(self):
+        return self.separator
+
+    def get_bar_char(self):
+        return self.bar_char
+
+    def get_bar(self):
+        char = self.get_bar_char()
+        perc = self.get_percentage()
+        length = int(self.width * perc / 100)
+        bar = char * length
+        bar = bar.ljust(self.width)
+        return bar
+
+    def get_elements(self):
+        return self.elements
+
+    def get_template(self):
+        separator = self.get_separator()
+        elements = self.get_elements()
+        return Template(separator.join((('$%s' % e) for e in elements)))
+
+    def get_total_time(self, current_time=None):
+        if current_time is None:
+            current_time = datetime.datetime.now()
+        if not self.started:
+            return datetime.timedelta()
+        return current_time - self.started
+
+    def get_rendered_total_time(self):
+        delta = self.get_total_time()
+        if not delta:
+            ttime = '-'
+        else:
+            ttime = str(delta)
+        return '%s %s' % (self.time_label, ttime)
+
+    def get_eta(self, current_time=None):
+        if current_time is None:
+            current_time = datetime.datetime.now()
+        if self.step == 0:
+            return datetime.timedelta()
+        total_seconds = get_total_seconds(self.get_total_time())
+        eta_seconds = total_seconds * self.steps / self.step - total_seconds
+        return datetime.timedelta(seconds=int(eta_seconds))
+
+    def get_rendered_eta(self):
+        eta = self.get_eta()
+        if not eta:
+            eta = '--:--:--'
+        else:
+            eta = str(eta).rjust(8)
+        return '%s: %s' % (self.eta_label, eta)
+
+    def get_percentage(self):
+        return float(self.step) / self.steps * 100
+
+    def get_rendered_percentage(self):
+        perc = self.get_percentage()
+        return ('%s%%' % (int(perc))).rjust(5)
+
+    def get_rendered_steps(self):
+        return '%s: %s/%s' % (self.steps_label, self.step, self.steps)
+
+    def get_rendered_speed(self, step=None, total_seconds=None):
+        if step is None:
+            step = self.step
+        if total_seconds is None:
+            total_seconds = get_total_seconds(self.get_total_time())
+        if step <= 0 or total_seconds <= 0:
+            speed = '-'
+        else:
+            speed = filesizeformat(float(step) / total_seconds)
+        return '%s: %s/s' % (self.speed_label, speed)
+
+    def get_rendered_transfer(self, step=None, steps=None):
+        if step is None:
+            step = self.step
+        if steps is None:
+            steps = self.steps
+
+        if steps <= 0:
+            return '%s: -' % self.transfer_label
+        total = filesizeformat(float(steps))
+        if step <= 0:
+            transferred = '-'
+        else:
+            transferred = filesizeformat(float(step))
+        return '%s: %s / %s' % (self.transfer_label, transferred, total)
+
+    def get_context(self):
+        return {
+            'percentage': self.get_rendered_percentage(),
+            'bar': self.get_bar(),
+            'steps': self.get_rendered_steps(),
+            'time': self.get_rendered_total_time(),
+            'eta': self.get_rendered_eta(),
+            'speed': self.get_rendered_speed(),
+            'transfer': self.get_rendered_transfer(),
+        }
+
+    def get_line(self):
+        template = self.get_template()
+        context = self.get_context()
+        return template.safe_substitute(**context)
+
+    def write(self, data):
+        self.stream.write(data)
+
+    def render(self, step):
+        if not self.started:
+            self.started = datetime.datetime.now()
+        if self.finished:
+            raise AlreadyFinishedError
+        self.step = step
+        self.write('\r%s' % self)
+        if step == self.steps:
+            self.finished = True
+        if step == self.steps:
+            self.write('\n')
+
+
+"""
+termcolors.py
+
+Grabbed from Django (http://www.djangoproject.com)
+"""
+
+color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
+foreground = dict([(color_names[x], '3%s' % x) for x in range(8)])
+background = dict([(color_names[x], '4%s' % x) for x in range(8)])
+
+RESET = '0'
+opt_dict = {'bold': '1', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'}
+
+def colorize(text='', opts=(), **kwargs):
+    """
+    Returns your text, enclosed in ANSI graphics codes.
+
+    Depends on the keyword arguments 'fg' and 'bg', and the contents of
+    the opts tuple/list.
+
+    Returns the RESET code if no parameters are given.
+
+    Valid colors:
+        'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'
+
+    Valid options:
+        'bold'
+        'underscore'
+        'blink'
+        'reverse'
+        'conceal'
+        'noreset' - string will not be auto-terminated with the RESET code
+
+    Examples:
+        colorize('hello', fg='red', bg='blue', opts=('blink',))
+        colorize()
+        colorize('goodbye', opts=('underscore',))
+        print colorize('first line', fg='red', opts=('noreset',))
+        print 'this should be red too'
+        print colorize('and so should this')
+        print 'this should not be red'
+    """
+    code_list = []
+    if text == '' and len(opts) == 1 and opts[0] == 'reset':
+        return '\x1b[%sm' % RESET
+    for k, v in kwargs.iteritems():
+        if k == 'fg':
+            code_list.append(foreground[v])
+        elif k == 'bg':
+            code_list.append(background[v])
+    for o in opts:
+        if o in opt_dict:
+            code_list.append(opt_dict[o])
+    if 'noreset' not in opts:
+        text = text + '\x1b[%sm' % RESET
+    return ('\x1b[%sm' % ';'.join(code_list)) + text
+
+def make_style(opts=(), **kwargs):
+    """
+    Returns a function with default parameters for colorize()
+
+    Example:
+        bold_red = make_style(opts=('bold',), fg='red')
+        print bold_red('hello')
+        KEYWORD = make_style(fg='yellow')
+        COMMENT = make_style(fg='blue', opts=('bold',))
+    """
+    return lambda text: colorize(text, opts, **kwargs)
+
+NOCOLOR_PALETTE = 'nocolor'
+DARK_PALETTE = 'dark'
+LIGHT_PALETTE = 'light'
+
+PALETTES = {
+    NOCOLOR_PALETTE: {
+        'ERROR':        {},
+        'NOTICE':       {},
+        'SQL_FIELD':    {},
+        'SQL_COLTYPE':  {},
+        'SQL_KEYWORD':  {},
+        'SQL_TABLE':    {},
+        'HTTP_INFO':         {},
+        'HTTP_SUCCESS':      {},
+        'HTTP_REDIRECT':     {},
+        'HTTP_NOT_MODIFIED': {},
+        'HTTP_BAD_REQUEST':  {},
+        'HTTP_NOT_FOUND':    {},
+        'HTTP_SERVER_ERROR': {},
+    },
+    DARK_PALETTE: {
+        'ERROR':        { 'fg': 'red', 'opts': ('bold',) },
+        'NOTICE':       { 'fg': 'red' },
+        'SQL_FIELD':    { 'fg': 'green', 'opts': ('bold',) },
+        'SQL_COLTYPE':  { 'fg': 'green' },
+        'SQL_KEYWORD':  { 'fg': 'yellow' },
+        'SQL_TABLE':    { 'opts': ('bold',) },
+        'HTTP_INFO':         { 'opts': ('bold',) },
+        'HTTP_SUCCESS':      { },
+        'HTTP_REDIRECT':     { 'fg': 'green' },
+        'HTTP_NOT_MODIFIED': { 'fg': 'cyan' },
+        'HTTP_BAD_REQUEST':  { 'fg': 'red', 'opts': ('bold',) },
+        'HTTP_NOT_FOUND':    { 'fg': 'yellow' },
+        'HTTP_SERVER_ERROR': { 'fg': 'magenta', 'opts': ('bold',) },
+    },
+    LIGHT_PALETTE: {
+        'ERROR':        { 'fg': 'red', 'opts': ('bold',) },
+        'NOTICE':       { 'fg': 'red' },
+        'SQL_FIELD':    { 'fg': 'green', 'opts': ('bold',) },
+        'SQL_COLTYPE':  { 'fg': 'green' },
+        'SQL_KEYWORD':  { 'fg': 'blue' },
+        'SQL_TABLE':    { 'opts': ('bold',) },
+        'HTTP_INFO':         { 'opts': ('bold',) },
+        'HTTP_SUCCESS':      { },
+        'HTTP_REDIRECT':     { 'fg': 'green', 'opts': ('bold',) },
+        'HTTP_NOT_MODIFIED': { 'fg': 'green' },
+        'HTTP_BAD_REQUEST':  { 'fg': 'red', 'opts': ('bold',) },
+        'HTTP_NOT_FOUND':    { 'fg': 'red' },
+        'HTTP_SERVER_ERROR': { 'fg': 'magenta', 'opts': ('bold',) },
+    }
+}
+DEFAULT_PALETTE = DARK_PALETTE
+
+# ---------------------------- #
+# --- End of termcolors.py --- #
+# ---------------------------- #
+
+
+class ColoredProgressBar(ProgressBar):
+
+    BAR_COLORS = (
+        (10, 'red'),
+        (30, 'magenta'),
+        (50, 'yellow'),
+        (99, 'green'),
+        (100, 'blue'),
+    )
+
+    def get_line(self):
+        line = super(ColoredProgressBar, self).get_line()
+        perc = self.get_percentage()
+        if perc > 100:
+            color = 'blue'
+        for max_perc, color in self.BAR_COLORS:
+            if perc <= max_perc:
+                break
+        return colorize(line, fg=color)
+
+
+class AnimatedProgressBar(ProgressBar):
+
+    def get_bar_char(self):
+        chars = '-/|\\'
+        if self.step >= self.steps:
+            return '='
+        return chars[self.step % len(chars)]
+
+
+class BarOnlyProgressBar(ProgressBar):
+
+    default_elements = ['bar', 'steps']
+
+    def get_bar(self):
+        bar = super(BarOnlyProgressBar, self).get_bar()
+        perc = self.get_percentage()
+        perc_text = '%s%%' % int(perc)
+        text = (' %s%% ' % (perc_text)).center(self.width, '=')
+        L = text.find(' ')
+        R = text.rfind(' ')
+        bar = ' '.join((bar[:L], perc_text, bar[R:]))
+        return bar
+
+
+class AnimatedColoredProgressBar(AnimatedProgressBar,
+                                 ColoredProgressBar):
+    pass
+
+
+class BarOnlyColoredProgressBar(ColoredProgressBar,
+                                BarOnlyProgressBar):
+    pass
+
+
+
+def main():
+    import time
+
+    print "Standard progress bar..."
+    bar = ProgressBar(30)
+    for x in xrange(1, 31):
+            bar.render(x)
+            time.sleep(0.02)
+    bar.stream.write('\n')
+    print
+
+    print "Empty bar..."
+    bar = ProgressBar(50)
+    bar.render(0)
+    print
+    print
+
+    print "Colored bar..."
+    bar = ColoredProgressBar(20)
+    for x in bar:
+        time.sleep(0.01)
+    print
+
+    print "Animated char bar..."
+    bar = AnimatedProgressBar(20)
+    for x in bar:
+        time.sleep(0.01)
+    print
+
+    print "Animated + colored char bar..."
+    bar = AnimatedColoredProgressBar(20)
+    for x in bar:
+        time.sleep(0.01)
+    print
+
+    print "Bar only ..."
+    bar = BarOnlyProgressBar(20)
+    for x in bar:
+        time.sleep(0.01)
+    print
+
+    print "Colored, longer bar-only, eta, total time ..."
+    bar = BarOnlyColoredProgressBar(40)
+    bar.width = 60
+    bar.elements += ['time', 'eta']
+    for x in bar:
+        time.sleep(0.01)
+    print
+    print
+
+    print "File transfer bar, breaks after 2 seconds ..."
+    total_bytes = 1024 * 1024 * 2
+    bar = ProgressBar(total_bytes)
+    bar.width = 50
+    bar.elements.remove('steps')
+    bar.elements += ['transfer', 'time', 'eta', 'speed']
+    for x in xrange(0, bar.steps, 1024):
+        bar.render(x)
+        time.sleep(0.01)
+        now = datetime.datetime.now()
+        if now - bar.started >= datetime.timedelta(seconds=2):
+            break
+    print
+    print
+
+
+
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/vcs/utils/termcolors.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,200 @@
+"""
+termcolors.py
+
+Grabbed from Django (http://www.djangoproject.com)
+"""
+
+color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
+foreground = dict([(color_names[x], '3%s' % x) for x in range(8)])
+background = dict([(color_names[x], '4%s' % x) for x in range(8)])
+
+RESET = '0'
+opt_dict = {'bold': '1', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'}
+
+def colorize(text='', opts=(), **kwargs):
+    """
+    Returns your text, enclosed in ANSI graphics codes.
+
+    Depends on the keyword arguments 'fg' and 'bg', and the contents of
+    the opts tuple/list.
+
+    Returns the RESET code if no parameters are given.
+
+    Valid colors:
+        'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'
+
+    Valid options:
+        'bold'
+        'underscore'
+        'blink'
+        'reverse'
+        'conceal'
+        'noreset' - string will not be auto-terminated with the RESET code
+
+    Examples:
+        colorize('hello', fg='red', bg='blue', opts=('blink',))
+        colorize()
+        colorize('goodbye', opts=('underscore',))
+        print colorize('first line', fg='red', opts=('noreset',))
+        print 'this should be red too'
+        print colorize('and so should this')
+        print 'this should not be red'
+    """
+    code_list = []
+    if text == '' and len(opts) == 1 and opts[0] == 'reset':
+        return '\x1b[%sm' % RESET
+    for k, v in kwargs.iteritems():
+        if k == 'fg':
+            code_list.append(foreground[v])
+        elif k == 'bg':
+            code_list.append(background[v])
+    for o in opts:
+        if o in opt_dict:
+            code_list.append(opt_dict[o])
+    if 'noreset' not in opts:
+        text = text + '\x1b[%sm' % RESET
+    return ('\x1b[%sm' % ';'.join(code_list)) + text
+
+def make_style(opts=(), **kwargs):
+    """
+    Returns a function with default parameters for colorize()
+
+    Example:
+        bold_red = make_style(opts=('bold',), fg='red')
+        print bold_red('hello')
+        KEYWORD = make_style(fg='yellow')
+        COMMENT = make_style(fg='blue', opts=('bold',))
+    """
+    return lambda text: colorize(text, opts, **kwargs)
+
+NOCOLOR_PALETTE = 'nocolor'
+DARK_PALETTE = 'dark'
+LIGHT_PALETTE = 'light'
+
+PALETTES = {
+    NOCOLOR_PALETTE: {
+        'ERROR':        {},
+        'NOTICE':       {},
+        'SQL_FIELD':    {},
+        'SQL_COLTYPE':  {},
+        'SQL_KEYWORD':  {},
+        'SQL_TABLE':    {},
+        'HTTP_INFO':         {},
+        'HTTP_SUCCESS':      {},
+        'HTTP_REDIRECT':     {},
+        'HTTP_NOT_MODIFIED': {},
+        'HTTP_BAD_REQUEST':  {},
+        'HTTP_NOT_FOUND':    {},
+        'HTTP_SERVER_ERROR': {},
+    },
+    DARK_PALETTE: {
+        'ERROR':        { 'fg': 'red', 'opts': ('bold',) },
+        'NOTICE':       { 'fg': 'red' },
+        'SQL_FIELD':    { 'fg': 'green', 'opts': ('bold',) },
+        'SQL_COLTYPE':  { 'fg': 'green' },
+        'SQL_KEYWORD':  { 'fg': 'yellow' },
+        'SQL_TABLE':    { 'opts': ('bold',) },
+        'HTTP_INFO':         { 'opts': ('bold',) },
+        'HTTP_SUCCESS':      { },
+        'HTTP_REDIRECT':     { 'fg': 'green' },
+        'HTTP_NOT_MODIFIED': { 'fg': 'cyan' },
+        'HTTP_BAD_REQUEST':  { 'fg': 'red', 'opts': ('bold',) },
+        'HTTP_NOT_FOUND':    { 'fg': 'yellow' },
+        'HTTP_SERVER_ERROR': { 'fg': 'magenta', 'opts': ('bold',) },
+    },
+    LIGHT_PALETTE: {
+        'ERROR':        { 'fg': 'red', 'opts': ('bold',) },
+        'NOTICE':       { 'fg': 'red' },
+        'SQL_FIELD':    { 'fg': 'green', 'opts': ('bold',) },
+        'SQL_COLTYPE':  { 'fg': 'green' },
+        'SQL_KEYWORD':  { 'fg': 'blue' },
+        'SQL_TABLE':    { 'opts': ('bold',) },
+        'HTTP_INFO':         { 'opts': ('bold',) },
+        'HTTP_SUCCESS':      { },
+        'HTTP_REDIRECT':     { 'fg': 'green', 'opts': ('bold',) },
+        'HTTP_NOT_MODIFIED': { 'fg': 'green' },
+        'HTTP_BAD_REQUEST':  { 'fg': 'red', 'opts': ('bold',) },
+        'HTTP_NOT_FOUND':    { 'fg': 'red' },
+        'HTTP_SERVER_ERROR': { 'fg': 'magenta', 'opts': ('bold',) },
+    }
+}
+DEFAULT_PALETTE = DARK_PALETTE
+
+def parse_color_setting(config_string):
+    """Parse a DJANGO_COLORS environment variable to produce the system palette
+
+    The general form of a pallete definition is:
+
+        "palette;role=fg;role=fg/bg;role=fg,option,option;role=fg/bg,option,option"
+
+    where:
+        palette is a named palette; one of 'light', 'dark', or 'nocolor'.
+        role is a named style used by Django
+        fg is a background color.
+        bg is a background color.
+        option is a display options.
+
+    Specifying a named palette is the same as manually specifying the individual
+    definitions for each role. Any individual definitions following the pallete
+    definition will augment the base palette definition.
+
+    Valid roles:
+        'error', 'notice', 'sql_field', 'sql_coltype', 'sql_keyword', 'sql_table',
+        'http_info', 'http_success', 'http_redirect', 'http_bad_request',
+        'http_not_found', 'http_server_error'
+
+    Valid colors:
+        'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'
+
+    Valid options:
+        'bold', 'underscore', 'blink', 'reverse', 'conceal'
+
+    """
+    if not config_string:
+        return PALETTES[DEFAULT_PALETTE]
+
+    # Split the color configuration into parts
+    parts = config_string.lower().split(';')
+    palette = PALETTES[NOCOLOR_PALETTE].copy()
+    for part in parts:
+        if part in PALETTES:
+            # A default palette has been specified
+            palette.update(PALETTES[part])
+        elif '=' in part:
+            # Process a palette defining string
+            definition = {}
+
+            # Break the definition into the role,
+            # plus the list of specific instructions.
+            # The role must be in upper case
+            role, instructions = part.split('=')
+            role = role.upper()
+
+            styles = instructions.split(',')
+            styles.reverse()
+
+            # The first instruction can contain a slash
+            # to break apart fg/bg.
+            colors = styles.pop().split('/')
+            colors.reverse()
+            fg = colors.pop()
+            if fg in color_names:
+                definition['fg'] = fg
+            if colors and colors[-1] in color_names:
+                definition['bg'] = colors[-1]
+
+            # All remaining instructions are options
+            opts = tuple(s for s in styles if s in opt_dict.keys())
+            if opts:
+                definition['opts'] = opts
+
+            # The nocolor palette has all available roles.
+            # Use that palette as the basis for determining
+            # if the role is valid.
+            if role in PALETTES[NOCOLOR_PALETTE] and definition:
+                palette[role] = definition
+
+    # If there are no colors specified, return the empty palette.
+    if palette == PALETTES[NOCOLOR_PALETTE]:
+        return None
+    return palette
--- a/rhodecode/model/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/model/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Nov 25, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
     :license: GPLv3, see COPYING for more details.
 
 
@@ -56,12 +56,13 @@
 
     :param engine: engine to bind to
     """
-    log.info("initializing db for %s", engine)
+    log.info("initializing db for %s" % engine)
     meta.Base.metadata.bind = engine
 
 
 class BaseModel(object):
-    """Base Model for all RhodeCode models, it adds sql alchemy session
+    """
+    Base Model for all RhodeCode models, it adds sql alchemy session
     into instance of model
 
     :param sa: If passed it reuses this session instead of creating a new one
@@ -72,3 +73,26 @@
             self.sa = sa
         else:
             self.sa = meta.Session
+
+    def _get_instance(self, cls, instance, callback=None):
+        """
+        Get's instance of given cls using some simple lookup mechanism.
+
+        :param cls: class to fetch
+        :param instance: int or Instance
+        :param callback: callback to call if all lookups failed
+        """
+
+        if isinstance(instance, cls):
+            return instance
+        elif isinstance(instance, int) 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))
+                    )
+                else:
+                    return callback(instance)
--- a/rhodecode/model/caching_query.py	Sun Feb 19 20:21:14 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,289 +0,0 @@
-"""caching_query.py
-
-Represent persistence structures which allow the usage of
-Beaker caching with SQLAlchemy.
-
-The three new concepts introduced here are:
-
- * CachingQuery - a Query subclass that caches and
-   retrieves results in/from Beaker.
- * FromCache - a query option that establishes caching
-   parameters on a Query
- * RelationshipCache - a variant of FromCache which is specific
-   to a query invoked during a lazy load.
- * _params_from_query - extracts value parameters from
-   a Query.
-
-The rest of what's here are standard SQLAlchemy and
-Beaker constructs.
-
-"""
-import beaker
-from beaker.exceptions import BeakerException
-
-from sqlalchemy.orm.interfaces import MapperOption
-from sqlalchemy.orm.query import Query
-from sqlalchemy.sql import visitors
-
-
-class CachingQuery(Query):
-    """A Query subclass which optionally loads full results from a Beaker
-    cache region.
-
-    The CachingQuery stores additional state that allows it to consult
-    a Beaker cache before accessing the database:
-
-    * A "region", which is a cache region argument passed to a
-      Beaker CacheManager, specifies a particular cache configuration
-      (including backend implementation, expiration times, etc.)
-    * A "namespace", which is a qualifying name that identifies a
-      group of keys within the cache.  A query that filters on a name
-      might use the name "by_name", a query that filters on a date range
-      to a joined table might use the name "related_date_range".
-
-    When the above state is present, a Beaker cache is retrieved.
-
-    The "namespace" name is first concatenated with
-    a string composed of the individual entities and columns the Query
-    requests, i.e. such as ``Query(User.id, User.name)``.
-
-    The Beaker cache is then loaded from the cache manager based
-    on the region and composed namespace.  The key within the cache
-    itself is then constructed against the bind parameters specified
-    by this query, which are usually literals defined in the
-    WHERE clause.
-
-    The FromCache and RelationshipCache mapper options below represent
-    the "public" method of configuring this state upon the CachingQuery.
-
-    """
-
-    def __init__(self, manager, *args, **kw):
-        self.cache_manager = manager
-        Query.__init__(self, *args, **kw)
-
-    def __iter__(self):
-        """override __iter__ to pull results from Beaker
-           if particular attributes have been configured.
-
-           Note that this approach does *not* detach the loaded objects from
-           the current session. If the cache backend is an in-process cache
-           (like "memory") and lives beyond the scope of the current session's
-           transaction, those objects may be expired. The method here can be
-           modified to first expunge() each loaded item from the current
-           session before returning the list of items, so that the items
-           in the cache are not the same ones in the current Session.
-
-        """
-        if hasattr(self, '_cache_parameters'):
-            return self.get_value(createfunc=lambda:
-                                  list(Query.__iter__(self)))
-        else:
-            return Query.__iter__(self)
-
-    def invalidate(self):
-        """Invalidate the value represented by this Query."""
-
-        cache, cache_key = _get_cache_parameters(self)
-        cache.remove(cache_key)
-
-    def get_value(self, merge=True, createfunc=None):
-        """Return the value from the cache for this query.
-
-        Raise KeyError if no value present and no
-        createfunc specified.
-
-        """
-        cache, cache_key = _get_cache_parameters(self)
-        ret = cache.get_value(cache_key, createfunc=createfunc)
-        if merge:
-            ret = self.merge_result(ret, load=False)
-        return ret
-
-    def set_value(self, value):
-        """Set the value in the cache for this query."""
-
-        cache, cache_key = _get_cache_parameters(self)
-        cache.put(cache_key, value)
-
-
-def query_callable(manager, query_cls=CachingQuery):
-    def query(*arg, **kw):
-        return query_cls(manager, *arg, **kw)
-    return query
-
-
-def get_cache_region(name, region):
-    if region not in beaker.cache.cache_regions:
-        raise BeakerException('Cache region `%s` not configured '
-            'Check if proper cache settings are in the .ini files' % region)
-    kw = beaker.cache.cache_regions[region]
-    return beaker.cache.Cache._get_cache(name, kw)
-
-
-def _get_cache_parameters(query):
-    """For a query with cache_region and cache_namespace configured,
-    return the correspoinding Cache instance and cache key, based
-    on this query's current criterion and parameter values.
-
-    """
-    if not hasattr(query, '_cache_parameters'):
-        raise ValueError("This Query does not have caching "
-                         "parameters configured.")
-
-    region, namespace, cache_key = query._cache_parameters
-
-    namespace = _namespace_from_query(namespace, query)
-
-    if cache_key is None:
-        # cache key - the value arguments from this query's parameters.
-        args = _params_from_query(query)
-        cache_key = " ".join([str(x) for x in args])
-
-    # get cache
-    #cache = query.cache_manager.get_cache_region(namespace, region)
-    cache = get_cache_region(namespace, region)
-    # optional - hash the cache_key too for consistent length
-    # import uuid
-    # cache_key= str(uuid.uuid5(uuid.NAMESPACE_DNS, cache_key))
-
-    return cache, cache_key
-
-
-def _namespace_from_query(namespace, query):
-    # cache namespace - the token handed in by the
-    # option + class we're querying against
-    namespace = " ".join([namespace] + [str(x) for x in query._entities])
-
-    # memcached wants this
-    namespace = namespace.replace(' ', '_')
-
-    return namespace
-
-
-def _set_cache_parameters(query, region, namespace, cache_key):
-
-    if hasattr(query, '_cache_parameters'):
-        region, namespace, cache_key = query._cache_parameters
-        raise ValueError("This query is already configured "
-                        "for region %r namespace %r" %
-                        (region, namespace)
-                    )
-    query._cache_parameters = region, namespace, cache_key
-
-
-class FromCache(MapperOption):
-    """Specifies that a Query should load results from a cache."""
-
-    propagate_to_loaders = False
-
-    def __init__(self, region, namespace, cache_key=None):
-        """Construct a new FromCache.
-
-        :param region: the cache region.  Should be a
-        region configured in the Beaker CacheManager.
-
-        :param namespace: the cache namespace.  Should
-        be a name uniquely describing the target Query's
-        lexical structure.
-
-        :param cache_key: optional.  A string cache key
-        that will serve as the key to the query.   Use this
-        if your query has a huge amount of parameters (such
-        as when using in_()) which correspond more simply to
-        some other identifier.
-
-        """
-        self.region = region
-        self.namespace = namespace
-        self.cache_key = cache_key
-
-    def process_query(self, query):
-        """Process a Query during normal loading operation."""
-
-        _set_cache_parameters(query, self.region, self.namespace,
-                              self.cache_key)
-
-
-class RelationshipCache(MapperOption):
-    """Specifies that a Query as called within a "lazy load"
-       should load results from a cache."""
-
-    propagate_to_loaders = True
-
-    def __init__(self, region, namespace, attribute):
-        """Construct a new RelationshipCache.
-
-        :param region: the cache region.  Should be a
-        region configured in the Beaker CacheManager.
-
-        :param namespace: the cache namespace.  Should
-        be a name uniquely describing the target Query's
-        lexical structure.
-
-        :param attribute: A Class.attribute which
-        indicates a particular class relationship() whose
-        lazy loader should be pulled from the cache.
-
-        """
-        self.region = region
-        self.namespace = namespace
-        self._relationship_options = {
-            (attribute.property.parent.class_, attribute.property.key): self
-        }
-
-    def process_query_conditionally(self, query):
-        """Process a Query that is used within a lazy loader.
-
-        (the process_query_conditionally() method is a SQLAlchemy
-        hook invoked only within lazyload.)
-
-        """
-        if query._current_path:
-            mapper, key = query._current_path[-2:]
-
-            for cls in mapper.class_.__mro__:
-                if (cls, key) in self._relationship_options:
-                    relationship_option = \
-                        self._relationship_options[(cls, key)]
-                    _set_cache_parameters(
-                            query,
-                            relationship_option.region,
-                            relationship_option.namespace,
-                            None)
-
-    def and_(self, option):
-        """Chain another RelationshipCache option to this one.
-
-        While many RelationshipCache objects can be specified on a single
-        Query separately, chaining them together allows for a more efficient
-        lookup during load.
-
-        """
-        self._relationship_options.update(option._relationship_options)
-        return self
-
-
-def _params_from_query(query):
-    """Pull the bind parameter values from a query.
-
-    This takes into account any scalar attribute bindparam set up.
-
-    E.g. params_from_query(query.filter(Cls.foo==5).filter(Cls.bar==7)))
-    would return [5, 7].
-
-    """
-    v = []
-    def visit_bindparam(bind):
-        value = query._params.get(bind.key, bind.value)
-
-        # lazyloader may dig a callable in here, intended
-        # to late-evaluate params after autoflush is called.
-        # convert to a scalar value.
-        if callable(value):
-            value = value()
-
-        v.append(value)
-    if query._criterion is not None:
-        visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam})
-    return v
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/model/comment.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.model.comment
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+    comments model for RhodeCode
+
+    :created_on: Nov 11, 2011
+    :author: marcink
+    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import traceback
+
+from pylons.i18n.translation import _
+from sqlalchemy.util.compat import defaultdict
+
+from rhodecode.lib 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
+from rhodecode.model.notification import NotificationModel
+
+log = logging.getLogger(__name__)
+
+
+class ChangesetCommentsModel(BaseModel):
+
+    def __get_changeset_comment(self, changeset_comment):
+        return self._get_instance(ChangesetComment, changeset_comment)
+
+    def _extract_mentions(self, s):
+        user_objects = []
+        for username in extract_mentioned_users(s):
+            user_obj = User.get_by_username(username, case_insensitive=True)
+            if user_obj:
+                user_objects.append(user_obj)
+        return user_objects
+
+    def create(self, text, repo_id, user_id, revision, f_path=None,
+               line_no=None):
+        """
+        Creates new comment for changeset
+
+        :param text:
+        :param repo_id:
+        :param user_id:
+        :param revision:
+        :param f_path:
+        :param line_no:
+        """
+        if text:
+            repo = Repository.get(repo_id)
+            cs = repo.scm_instance.get_changeset(revision)
+            desc = cs.message
+            author = cs.author_email
+            comment = ChangesetComment()
+            comment.repo = repo
+            comment.user_id = user_id
+            comment.revision = revision
+            comment.text = text
+            comment.f_path = f_path
+            comment.line_no = line_no
+
+            self.sa.add(comment)
+            self.sa.flush()
+
+            # make notification
+            line = ''
+            if line_no:
+                line = _('on line %s') % line_no
+            subj = h.link_to('Re commit: %(commit_desc)s %(line)s' % \
+                                    {'commit_desc': desc, 'line': line},
+                             h.url('changeset_home', repo_name=repo.repo_name,
+                                   revision=revision,
+                                   anchor='comment-%s' % comment.comment_id,
+                                   qualified=True,
+                                   )
+                             )
+            body = text
+            recipients = ChangesetComment.get_users(revision=revision)
+            # add changeset author
+            recipients += [User.get_by_email(author)]
+
+            NotificationModel().create(created_by=user_id, subject=subj,
+                                   body=body, recipients=recipients,
+                                   type_=Notification.TYPE_CHANGESET_COMMENT)
+
+            mention_recipients = set(self._extract_mentions(body))\
+                                    .difference(recipients)
+            if mention_recipients:
+                subj = _('[Mention]') + ' ' + subj
+                NotificationModel().create(created_by=user_id, subject=subj,
+                                    body=body,
+                                    recipients=mention_recipients,
+                                    type_=Notification.TYPE_CHANGESET_COMMENT)
+
+            return comment
+
+    def delete(self, comment):
+        """
+        Deletes given comment
+
+        :param comment_id:
+        """
+        comment = self.__get_changeset_comment(comment)
+        self.sa.delete(comment)
+
+        return comment
+
+    def get_comments(self, repo_id, revision):
+        return ChangesetComment.query()\
+                .filter(ChangesetComment.repo_id == repo_id)\
+                .filter(ChangesetComment.revision == revision)\
+                .filter(ChangesetComment.line_no == None)\
+                .filter(ChangesetComment.f_path == None).all()
+
+    def get_inline_comments(self, repo_id, revision):
+        comments = self.sa.query(ChangesetComment)\
+            .filter(ChangesetComment.repo_id == repo_id)\
+            .filter(ChangesetComment.revision == revision)\
+            .filter(ChangesetComment.line_no != None)\
+            .filter(ChangesetComment.f_path != None).all()
+
+        paths = defaultdict(lambda: defaultdict(list))
+
+        for co in comments:
+            paths[co.f_path][co.line_no].append(co)
+        return paths.items()
--- a/rhodecode/model/db.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/model/db.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 08, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -27,25 +27,23 @@
 import logging
 import datetime
 import traceback
-from datetime import date
+from collections import defaultdict
 
 from sqlalchemy import *
 from sqlalchemy.ext.hybrid import hybrid_property
 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
 from beaker.cache import cache_region, region_invalidate
 
-from vcs import get_backend
-from vcs.utils.helpers import get_scm
-from vcs.exceptions import VCSError
-from vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
 
-from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
-    generate_api_key, safe_unicode
-from rhodecode.lib.exceptions import UsersGroupsAssignedException
+from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
 from rhodecode.lib.compat import json
+from rhodecode.lib.caching_query import FromCache
 
 from rhodecode.model.meta import Base, Session
-from rhodecode.model.caching_query import FromCache
 
 
 log = logging.getLogger(__name__)
@@ -87,8 +85,8 @@
 
 
 class BaseModel(object):
-    """Base Model for all classess
-
+    """
+    Base Model for all classess
     """
 
     @classmethod
@@ -97,7 +95,8 @@
         return class_mapper(cls).c.keys()
 
     def get_dict(self):
-        """return dict with keys and values corresponding
+        """
+        return dict with keys and values corresponding
         to this model data """
 
         d = {}
@@ -142,12 +141,14 @@
     def delete(cls, id_):
         obj = cls.query().get(id_)
         Session.delete(obj)
-        Session.commit()
 
 
-class RhodeCodeSettings(Base, BaseModel):
+class RhodeCodeSetting(Base, BaseModel):
     __tablename__ = 'rhodecode_settings'
-    __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
+    __table_args__ = (
+        UniqueConstraint('app_settings_name'),
+        {'extend_existing': True}
+    )
     app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
     _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -156,7 +157,6 @@
         self.app_settings_name = k
         self.app_settings_value = v
 
-
     @validates('_app_settings_value')
     def validate_settings_value(self, key, val):
         assert type(val) == unicode
@@ -165,7 +165,7 @@
     @hybrid_property
     def app_settings_value(self):
         v = self._app_settings_value
-        if v == 'ldap_active':
+        if self.app_settings_name == 'ldap_active':
             v = str2bool(v)
         return v
 
@@ -179,9 +179,10 @@
         self._app_settings_value = safe_unicode(val)
 
     def __repr__(self):
-        return "<%s('%s:%s')>" % (self.__class__.__name__,
-                                  self.app_settings_name, self.app_settings_value)
-
+        return "<%s('%s:%s')>" % (
+            self.__class__.__name__,
+            self.app_settings_name, self.app_settings_value
+        )
 
     @classmethod
     def get_by_name(cls, ldap_key):
@@ -218,7 +219,10 @@
 
 class RhodeCodeUi(Base, BaseModel):
     __tablename__ = 'rhodecode_ui'
-    __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
+    __table_args__ = (
+        UniqueConstraint('ui_key'),
+        {'extend_existing': True}
+    )
 
     HOOK_UPDATE = 'changegroup.update'
     HOOK_REPO_SIZE = 'changegroup.repo_size'
@@ -231,12 +235,10 @@
     ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
     ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
 
-
     @classmethod
     def get_by_key(cls, key):
         return cls.query().filter(cls.ui_key == key)
 
-
     @classmethod
     def get_builtin_hooks(cls):
         q = cls.query()
@@ -263,12 +265,14 @@
         new_ui.ui_value = val
 
         Session.add(new_ui)
-        Session.commit()
 
 
 class User(Base, BaseModel):
     __tablename__ = 'users'
-    __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
+    __table_args__ = (
+        UniqueConstraint('username'), UniqueConstraint('email'),
+        {'extend_existing': True}
+    )
     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)
     password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -286,10 +290,12 @@
 
     repositories = relationship('Repository')
     user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
+    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
 
     group_member = relationship('UsersGroupMember', cascade='all')
 
+    notifications = relationship('UserNotification',)
+
     @hybrid_property
     def email(self):
         return self._email
@@ -303,6 +309,11 @@
         return '%s %s' % (self.name, self.lastname)
 
     @property
+    def full_name_or_username(self):
+        return ('%s %s' % (self.name, self.lastname)
+                if (self.name and self.lastname) else self.username)
+
+    @property
     def full_contact(self):
         return '%s %s <%s>' % (self.name, self.lastname, self.email)
 
@@ -315,60 +326,64 @@
         return self.admin
 
     def __repr__(self):
-        try:
-            return "<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                             self.user_id, self.username)
-        except:
-            return self.__class__.__name__
+        return "<%s('id:%s:%s')>" % (self.__class__.__name__,
+                                     self.user_id, self.username)
 
-    def __json__(self):
-        return {'email': self.email}
+    @classmethod
+    def get_by_username(cls, username, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.username.ilike(username))
+        else:
+            q = cls.query().filter(cls.username == username)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_user_%s" % username))
+        return q.scalar()
 
     @classmethod
-    def get_by_username(cls, username, case_insensitive=False):
-        if case_insensitive:
-            return Session.query(cls).filter(cls.username.ilike(username)).scalar()
-        else:
-            return Session.query(cls).filter(cls.username == username).scalar()
+    def get_by_api_key(cls, api_key, cache=False):
+        q = cls.query().filter(cls.api_key == api_key)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_api_key_%s" % api_key))
+        return q.scalar()
 
     @classmethod
-    def get_by_api_key(cls, api_key):
-        return cls.query().filter(cls.api_key == api_key).one()
+    def get_by_email(cls, email, case_insensitive=False, cache=False):
+        if case_insensitive:
+            q = cls.query().filter(cls.email.ilike(email))
+        else:
+            q = cls.query().filter(cls.email == email)
+
+        if cache:
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_api_key_%s" % email))
+        return q.scalar()
 
     def update_lastlogin(self):
         """Update user lastlogin"""
-
         self.last_login = datetime.datetime.now()
         Session.add(self)
-        Session.commit()
-        log.debug('updated user %s lastlogin', self.username)
-
-    @classmethod
-    def create(cls, form_data):
-        from rhodecode.lib.auth import get_crypt_password
+        log.debug('updated user %s lastlogin' % self.username)
 
-        try:
-            new_user = cls()
-            for k, v in form_data.items():
-                if k == 'password':
-                    v = get_crypt_password(v)
-                setattr(new_user, k, v)
+    def __json__(self):
+        return dict(
+            email=self.email,
+            full_name=self.full_name,
+            full_name_or_username=self.full_name_or_username,
+            short_contact=self.short_contact,
+            full_contact=self.full_contact
+        )
 
-            new_user.api_key = generate_api_key(form_data['username'])
-            Session.add(new_user)
-            Session.commit()
-            return new_user
-        except:
-            log.error(traceback.format_exc())
-            Session.rollback()
-            raise
 
 class UserLog(Base, BaseModel):
     __tablename__ = 'user_logs'
-    __table_args__ = {'extend_existing':True}
+    __table_args__ = {'extend_existing': True}
     user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
     repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
     user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
     action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -376,15 +391,15 @@
 
     @property
     def action_as_day(self):
-        return date(*self.action_date.timetuple()[:3])
+        return datetime.date(*self.action_date.timetuple()[:3])
 
     user = relationship('User')
-    repository = relationship('Repository')
+    repository = relationship('Repository',cascade='')
 
 
 class UsersGroup(Base, BaseModel):
     __tablename__ = 'users_groups'
-    __table_args__ = {'extend_existing':True}
+    __table_args__ = {'extend_existing': True}
 
     users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
@@ -396,18 +411,16 @@
         return '<userGroup(%s)>' % (self.users_group_name)
 
     @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+    def get_by_group_name(cls, group_name, cache=False,
+                          case_insensitive=False):
         if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.users_group_name.ilike(group_name))
+            q = cls.query().filter(cls.users_group_name.ilike(group_name))
         else:
-            gr = cls.query()\
-                .filter(cls.users_group_name == group_name)
+            q = cls.query().filter(cls.users_group_name == group_name)
         if cache:
-            gr = gr.options(FromCache("sql_cache_short",
-                                          "get_user_%s" % group_name))
-        return gr.scalar()
-
+            q = q.options(FromCache("sql_cache_short",
+                                    "get_user_%s" % group_name))
+        return q.scalar()
 
     @classmethod
     def get(cls, users_group_id, cache=False):
@@ -417,71 +430,10 @@
                                     "get_users_group_%s" % users_group_id))
         return users_group.get(users_group_id)
 
-    @classmethod
-    def create(cls, form_data):
-        try:
-            new_users_group = cls()
-            for k, v in form_data.items():
-                setattr(new_users_group, k, v)
-
-            Session.add(new_users_group)
-            Session.commit()
-            return new_users_group
-        except:
-            log.error(traceback.format_exc())
-            Session.rollback()
-            raise
-
-    @classmethod
-    def update(cls, users_group_id, form_data):
-
-        try:
-            users_group = cls.get(users_group_id, cache=False)
-
-            for k, v in form_data.items():
-                if k == 'users_group_members':
-                    users_group.members = []
-                    Session.flush()
-                    members_list = []
-                    if v:
-                        v = [v] if isinstance(v, basestring) else v
-                        for u_id in set(v):
-                            member = UsersGroupMember(users_group_id, u_id)
-                            members_list.append(member)
-                    setattr(users_group, 'members', members_list)
-                setattr(users_group, k, v)
-
-            Session.add(users_group)
-            Session.commit()
-        except:
-            log.error(traceback.format_exc())
-            Session.rollback()
-            raise
-
-    @classmethod
-    def delete(cls, users_group_id):
-        try:
-
-            # check if this group is not assigned to repo
-            assigned_groups = UsersGroupRepoToPerm.query()\
-                .filter(UsersGroupRepoToPerm.users_group_id ==
-                        users_group_id).all()
-
-            if assigned_groups:
-                raise UsersGroupsAssignedException('Group assigned to %s' %
-                                                   assigned_groups)
-
-            users_group = cls.get(users_group_id, cache=False)
-            Session.delete(users_group)
-            Session.commit()
-        except:
-            log.error(traceback.format_exc())
-            Session.rollback()
-            raise
 
 class UsersGroupMember(Base, BaseModel):
     __tablename__ = 'users_groups_members'
-    __table_args__ = {'extend_existing':True}
+    __table_args__ = {'extend_existing': True}
 
     users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
@@ -494,18 +446,13 @@
         self.users_group_id = gr_id
         self.user_id = u_id
 
-    @staticmethod
-    def add_user_to_group(group, user):
-        ugm = UsersGroupMember()
-        ugm.users_group = group
-        ugm.user = user
-        Session.add(ugm)
-        Session.commit()
-        return ugm
 
 class Repository(Base, BaseModel):
     __tablename__ = 'repositories'
-    __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
+    __table_args__ = (
+        UniqueConstraint('repo_name'),
+        {'extend_existing': True},
+    )
 
     repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
@@ -521,17 +468,16 @@
     fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
     group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
 
-
     user = relationship('User')
     fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('Group')
-    repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
+    group = relationship('RepoGroup')
+    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
     users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
     stats = relationship('Statistics', cascade='all', uselist=False)
 
     followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
 
-    logs = relationship('UserLog', cascade='all')
+    logs = relationship('UserLog')
 
     def __repr__(self):
         return "<%s('%s:%s')>" % (self.__class__.__name__,
@@ -547,7 +493,7 @@
         q = q.options(joinedload(Repository.fork))\
                 .options(joinedload(Repository.user))\
                 .options(joinedload(Repository.group))
-        return q.one()
+        return q.scalar()
 
     @classmethod
     def get_repo_forks(cls, repo_id):
@@ -560,9 +506,9 @@
 
         :param cls:
         """
-        q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
-                                              cls.url_sep())
-        q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        q = Session.query(RhodeCodeUi)\
+            .filter(RhodeCodeUi.ui_key == cls.url_sep())
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
         return q.one().ui_value
 
     @property
@@ -598,7 +544,7 @@
         """
         q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
                                               Repository.url_sep())
-        q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
         return q.one().ui_value
 
     @property
@@ -633,7 +579,6 @@
         baseui._ucfg = config.config()
         baseui._tcfg = config.config()
 
-
         ret = RhodeCodeUi.query()\
             .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
 
@@ -651,14 +596,13 @@
         """
         returns True if given repo name is a valid filesystem repository
 
-        @param cls:
-        @param repo_name:
+        :param cls:
+        :param repo_name:
         """
         from rhodecode.lib.utils import is_valid_repo
 
         return is_valid_repo(repo_name, cls.base_path())
 
-
     #==========================================================================
     # SCM PROPERTIES
     #==========================================================================
@@ -678,35 +622,34 @@
     def last_change(self):
         return self.scm_instance.last_change
 
+    def comments(self, revisions=None):
+        """
+        Returns comments for this repository grouped by revisions
+
+        :param revisions: filter query by revisions only
+        """
+        cmts = ChangesetComment.query()\
+            .filter(ChangesetComment.repo == self)
+        if revisions:
+            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
+        grouped = defaultdict(list)
+        for cmt in cmts.all():
+            grouped[cmt.revision].append(cmt)
+        return grouped
+
     #==========================================================================
     # SCM CACHE INSTANCE
     #==========================================================================
 
     @property
     def invalidate(self):
-        """
-        Returns Invalidation object if this repo should be invalidated
-        None otherwise. `cache_active = False` means that this cache
-        state is not valid and needs to be invalidated
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_key == self.repo_name)\
-            .filter(CacheInvalidation.cache_active == False)\
-            .scalar()
+        return CacheInvalidation.invalidate(self.repo_name)
 
     def set_invalidate(self):
         """
         set a cache for invalidation for this instance
         """
-        inv = CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_key == self.repo_name)\
-            .scalar()
-
-        if inv is None:
-            inv = CacheInvalidation(self.repo_name)
-        inv.cache_active = True
-        Session.add(inv)
-        Session.commit()
+        CacheInvalidation.set_invalidate(self.repo_name)
 
     @LazyProperty
     def scm_instance(self):
@@ -717,28 +660,20 @@
         @cache_region('long_term')
         def _c(repo_name):
             return self.__get_instance()
-
-        # TODO: remove this trick when beaker 1.6 is released
-        # and have fixed this issue with not supporting unicode keys
-        rn = safe_str(self.repo_name)
-
+        rn = self.repo_name
+        log.debug('Getting cached instance of repo')
         inv = self.invalidate
         if inv is not None:
             region_invalidate(_c, None, rn)
             # update our cache
-            inv.cache_active = True
-            Session.add(inv)
-            Session.commit()
-
+            CacheInvalidation.set_valid(inv.cache_key)
         return _c(rn)
 
     def __get_instance(self):
-
         repo_full_path = self.repo_full_path
-
         try:
             alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository', alias)
+            log.debug('Creating instance of %s repository' % alias)
             backend = get_backend(alias)
         except VCSError:
             log.error(traceback.format_exc())
@@ -751,7 +686,7 @@
 
             repo = backend(safe_str(repo_full_path), create=False,
                            baseui=self._ui)
-            #skip hidden web repository
+            # skip hidden web repository
             if repo._get_hidden():
                 return
         else:
@@ -760,19 +695,24 @@
         return repo
 
 
-class Group(Base, BaseModel):
+class RepoGroup(Base, BaseModel):
     __tablename__ = 'groups'
-    __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
-                      CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
-    __mapper_args__ = {'order_by':'group_name'}
+    __table_args__ = (
+        UniqueConstraint('group_name', 'group_parent_id'),
+        CheckConstraint('group_id != group_parent_id'),
+        {'extend_existing': True},
+    )
+    __mapper_args__ = {'order_by': 'group_name'}
 
     group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
     group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
     group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
-    parent_group = relationship('Group', remote_side=group_id)
+    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
+    users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
 
+    parent_group = relationship('RepoGroup', remote_side=group_id)
 
     def __init__(self, group_name='', parent_group=None):
         self.group_name = group_name
@@ -838,11 +778,11 @@
 
     @property
     def children(self):
-        return Group.query().filter(Group.parent_group == self)
+        return RepoGroup.query().filter(RepoGroup.parent_group == self)
 
     @property
     def name(self):
-        return self.group_name.split(Group.url_sep())[-1]
+        return self.group_name.split(RepoGroup.url_sep())[-1]
 
     @property
     def full_path(self):
@@ -850,7 +790,7 @@
 
     @property
     def full_path_splitted(self):
-        return self.group_name.split(Group.url_sep())
+        return self.group_name.split(RepoGroup.url_sep())
 
     @property
     def repositories(self):
@@ -869,93 +809,100 @@
 
         return cnt + children_count(self)
 
-
     def get_new_name(self, group_name):
         """
         returns new full group name based on parent and new name
 
         :param group_name:
         """
-        path_prefix = (self.parent_group.full_path_splitted if 
+        path_prefix = (self.parent_group.full_path_splitted if
                        self.parent_group else [])
-        return Group.url_sep().join(path_prefix + [group_name])
+        return RepoGroup.url_sep().join(path_prefix + [group_name])
 
 
 class Permission(Base, BaseModel):
     __tablename__ = 'permissions'
-    __table_args__ = {'extend_existing':True}
+    __table_args__ = {'extend_existing': True}
     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')>" % (self.__class__.__name__,
-                                  self.permission_id, self.permission_name)
+        return "<%s('%s:%s')>" % (
+            self.__class__.__name__, self.permission_id, self.permission_name
+        )
 
     @classmethod
     def get_by_key(cls, key):
         return cls.query().filter(cls.permission_name == key).scalar()
 
-class RepoToPerm(Base, BaseModel):
+    @classmethod
+    def get_default_perms(cls, default_user_id):
+        q = Session.query(UserRepoToPerm, Repository, cls)\
+         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
+         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoToPerm.user_id == default_user_id)
+
+        return q.all()
+
+    @classmethod
+    def get_default_group_perms(cls, default_user_id):
+        q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
+         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
+         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
+         .filter(UserRepoGroupToPerm.user_id == default_user_id)
+
+        return q.all()
+
+
+class UserRepoToPerm(Base, BaseModel):
     __tablename__ = 'repo_to_perm'
-    __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
+    __table_args__ = (
+        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
+        {'extend_existing': True}
+    )
     repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
     permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
     repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
+    repository = relationship('Repository')
     permission = relationship('Permission')
-    repository = relationship('Repository')
+
+    @classmethod
+    def create(cls, user, repository, permission):
+        n = cls()
+        n.user = user
+        n.repository = repository
+        n.permission = permission
+        Session.add(n)
+        return n
+
+    def __repr__(self):
+        return '<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})
+    __table_args__ = (
+        UniqueConstraint('user_id', 'permission_id'),
+        {'extend_existing': True}
+    )
     user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
     permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
-    permission = relationship('Permission')
-
-    @classmethod
-    def has_perm(cls, user_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
-
-        return cls.query().filter(cls.user_id == user_id)\
-            .filter(cls.permission == perm).scalar() is not None
-
-    @classmethod
-    def grant_perm(cls, user_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
+    permission = relationship('Permission', lazy='joined')
 
-        new = cls()
-        new.user_id = user_id
-        new.permission = perm
-        try:
-            Session.add(new)
-            Session.commit()
-        except:
-            Session.rollback()
-
-
-    @classmethod
-    def revoke_perm(cls, user_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
-
-        try:
-            cls.query().filter(cls.user_id == user_id)\
-                .filter(cls.permission == perm).delete()
-            Session.commit()
-        except:
-            Session.rollback()
 
 class UsersGroupRepoToPerm(Base, BaseModel):
     __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
+    __table_args__ = (
+        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
+        {'extend_existing': True}
+    )
     users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
     permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
@@ -965,11 +912,25 @@
     permission = relationship('Permission')
     repository = relationship('Repository')
 
+    @classmethod
+    def create(cls, users_group, repository, permission):
+        n = cls()
+        n.users_group = users_group
+        n.repository = repository
+        n.permission = permission
+        Session.add(n)
+        return n
+
     def __repr__(self):
         return '<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}
+    )
     users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
     permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
@@ -978,60 +939,43 @@
     permission = relationship('Permission')
 
 
-    @classmethod
-    def has_perm(cls, users_group_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
-
-        return cls.query().filter(cls.users_group_id ==
-                                         users_group_id)\
-                                         .filter(cls.permission == perm)\
-                                         .scalar() is not None
-
-    @classmethod
-    def grant_perm(cls, users_group_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
-
-        new = cls()
-        new.users_group_id = users_group_id
-        new.permission = perm
-        try:
-            Session.add(new)
-            Session.commit()
-        except:
-            Session.rollback()
-
-
-    @classmethod
-    def revoke_perm(cls, users_group_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
-
-        try:
-            cls.query().filter(cls.users_group_id == users_group_id)\
-                .filter(cls.permission == perm).delete()
-            Session.commit()
-        except:
-            Session.rollback()
-
-
-class GroupToPerm(Base, BaseModel):
-    __tablename__ = 'group_to_perm'
-    __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
+class UserRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'user_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'group_id', 'permission_id'),
+        {'extend_existing': True}
+    )
 
     group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
     permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
+    group = relationship('RepoGroup')
     permission = relationship('Permission')
-    group = relationship('Group')
+
+
+class UsersGroupRepoGroupToPerm(Base, BaseModel):
+    __tablename__ = 'users_group_repo_group_to_perm'
+    __table_args__ = (
+        UniqueConstraint('users_group_id', 'group_id'),
+        {'extend_existing': True}
+    )
+
+    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group = relationship('UsersGroup')
+    permission = relationship('Permission')
+    group = relationship('RepoGroup')
+
 
 class Statistics(Base, BaseModel):
     __tablename__ = 'statistics'
-    __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
+    __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
     stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
     stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
@@ -1041,11 +985,14 @@
 
     repository = relationship('Repository', single_parent=True)
 
+
 class UserFollowing(Base, BaseModel):
     __tablename__ = 'user_followings'
-    __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
-                      UniqueConstraint('user_id', 'follows_user_id')
-                      , {'extend_existing':True})
+    __table_args__ = (
+        UniqueConstraint('user_id', 'follows_repository_id'),
+        UniqueConstraint('user_id', 'follows_user_id'),
+        {'extend_existing': True}
+    )
 
     user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
     user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
@@ -1058,20 +1005,19 @@
     follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
     follows_repository = relationship('Repository', order_by='Repository.repo_name')
 
-
     @classmethod
     def get_repo_followers(cls, repo_id):
         return cls.query().filter(cls.follows_repo_id == repo_id)
 
+
 class CacheInvalidation(Base, BaseModel):
     __tablename__ = 'cache_invalidation'
-    __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
+    __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
     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)
     cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
 
-
     def __init__(self, cache_key, cache_args=''):
         self.cache_key = cache_key
         self.cache_args = cache_args
@@ -1081,10 +1027,177 @@
         return "<%s('%s:%s')>" % (self.__class__.__name__,
                                   self.cache_id, self.cache_key)
 
+    @classmethod
+    def _get_key(cls, key):
+        """
+        Wrapper for generating a key
+
+        :param key:
+        """
+        import rhodecode
+        prefix = ''
+        iid = rhodecode.CONFIG.get('instance_id')
+        if iid:
+            prefix = iid 
+        return "%s%s" % (prefix, key)
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.cache_key == key).scalar()
+
+    @classmethod
+    def invalidate(cls, key):
+        """
+        Returns Invalidation object if this given key should be invalidated
+        None otherwise. `cache_active = False` means that this cache
+        state is not valid and needs to be invalidated
+
+        :param key:
+        """
+        return cls.query()\
+                .filter(CacheInvalidation.cache_key == key)\
+                .filter(CacheInvalidation.cache_active == False)\
+                .scalar()
+
+    @classmethod
+    def set_invalidate(cls, key):
+        """
+        Mark this Cache key for invalidation
+
+        :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)
+
+        try:
+            Session.add(inv_obj)
+            Session.commit()
+        except Exception:
+            log.error(traceback.format_exc())
+            Session.rollback()
+
+    @classmethod
+    def set_valid(cls, key):
+        """
+        Mark this cache key as active and currently cached
+
+        :param key:
+        """
+        inv_obj = cls.get_by_key(key)
+        inv_obj.cache_active = True
+        Session.add(inv_obj)
+        Session.commit()
+
+
+class ChangesetComment(Base, BaseModel):
+    __tablename__ = 'changeset_comments'
+    __table_args__ = ({'extend_existing': True},)
+    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
+    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    revision = Column('revision', String(40), nullable=False)
+    line_no = Column('line_no', Unicode(10), nullable=True)
+    f_path = Column('f_path', Unicode(1000), nullable=True)
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+    text = Column('text', Unicode(25000), nullable=False)
+    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+
+    @classmethod
+    def get_users(cls, revision):
+        """
+        Returns user associated with this changesetComment. ie those
+        who actually commented
+
+        :param cls:
+        :param revision:
+        """
+        return Session.query(User)\
+                .filter(cls.revision == revision)\
+                .join(ChangesetComment.author).all()
+
+
+class Notification(Base, BaseModel):
+    __tablename__ = 'notifications'
+    __table_args__ = ({'extend_existing': True},)
+
+    TYPE_CHANGESET_COMMENT = u'cs_comment'
+    TYPE_MESSAGE = u'message'
+    TYPE_MENTION = u'mention'
+    TYPE_REGISTRATION = u'registration'
+
+    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
+    subject = Column('subject', Unicode(512), nullable=True)
+    body = Column('body', Unicode(50000), nullable=True)
+    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    type_ = Column('type', Unicode(256))
+
+    created_by_user = relationship('User')
+    notifications_to_users = relationship('UserNotification', lazy='joined',
+                                          cascade="all, delete, delete-orphan")
+
+    @property
+    def recipients(self):
+        return [x.user for x in UserNotification.query()\
+                .filter(UserNotification.notification == self).all()]
+
+    @classmethod
+    def create(cls, created_by, subject, body, recipients, type_=None):
+        if type_ is None:
+            type_ = Notification.TYPE_MESSAGE
+
+        notification = cls()
+        notification.created_by_user = created_by
+        notification.subject = subject
+        notification.body = body
+        notification.type_ = type_
+        notification.created_on = datetime.datetime.now()
+
+        for u in recipients:
+            assoc = UserNotification()
+            assoc.notification = notification
+            u.notifications.append(assoc)
+        Session.add(notification)
+        return notification
+
+    @property
+    def description(self):
+        from rhodecode.model.notification import NotificationModel
+        return NotificationModel().make_description(self)
+
+
+class UserNotification(Base, BaseModel):
+    __tablename__ = 'user_to_notification'
+    __table_args__ = (
+        UniqueConstraint('user_id', 'notification_id'),
+        {'extend_existing': True}
+    )
+    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
+    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
+    read = Column('read', Boolean, default=False)
+    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
+
+    user = relationship('User', lazy="joined")
+    notification = relationship('Notification', lazy="joined",
+                                order_by=lambda: Notification.created_on.desc(),)
+
+    def mark_as_read(self):
+        self.read = True
+        Session.add(self)
+
+
 class DbMigrateVersion(Base, BaseModel):
     __tablename__ = 'db_migrate_version'
-    __table_args__ = {'extend_existing':True}
+    __table_args__ = {'extend_existing': True}
     repository_id = Column('repository_id', String(250), primary_key=True)
     repository_path = Column('repository_path', Text)
     version = Column('version', Integer)
-
--- a/rhodecode/model/forms.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/model/forms.py	Sun Feb 26 17:25:09 2012 +0200
@@ -36,28 +36,33 @@
 from rhodecode.lib.utils import repo_name_slug
 from rhodecode.lib.auth import authenticate, get_crypt_password
 from rhodecode.lib.exceptions import LdapImportError
-from rhodecode.model.user import UserModel
-from rhodecode.model.repo import RepoModel
-from rhodecode.model.db import User, UsersGroup, Group, Repository
+from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
 from rhodecode import BACKENDS
 
 log = logging.getLogger(__name__)
 
+
 #this is needed to translate the messages using _() in validators
 class State_obj(object):
     _ = staticmethod(_)
 
+
 #==============================================================================
 # VALIDATORS
 #==============================================================================
 class ValidAuthToken(formencode.validators.FancyValidator):
-    messages = {'invalid_token':_('Token mismatch')}
+    messages = {'invalid_token': _('Token mismatch')}
 
     def validate_python(self, value, state):
 
         if value != authentication_token():
-            raise formencode.Invalid(self.message('invalid_token', state,
-                                            search_number=value), value, state)
+            raise formencode.Invalid(
+                self.message('invalid_token',
+                             state, search_number=value),
+                value,
+                state
+            )
+
 
 def ValidUsername(edit, old_data):
     class _ValidUsername(formencode.validators.FancyValidator):
@@ -68,7 +73,7 @@
             #check if user is unique
             old_un = None
             if edit:
-                old_un = UserModel().get(old_data.get('user_id')).username
+                old_un = User.get(old_data.get('user_id')).username
 
             if old_un != value or not edit:
                 if User.get_by_username(value, case_insensitive=True):
@@ -76,11 +81,13 @@
                                                'exists') , value, state)
 
             if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
-                raise formencode.Invalid(_('Username may only contain '
-                                           'alphanumeric characters '
-                                           'underscores, periods or dashes '
-                                           'and must begin with alphanumeric '
-                                           'character'), value, state)
+                raise formencode.Invalid(
+                    _('Username may only contain alphanumeric characters '
+                      'underscores, periods or dashes and must begin with '
+                      'alphanumeric character'),
+                    value,
+                    state
+                )
 
     return _ValidUsername
 
@@ -102,16 +109,17 @@
                 if UsersGroup.get_by_group_name(value, cache=False,
                                                case_insensitive=True):
                     raise formencode.Invalid(_('This users group '
-                                               'already exists') , value,
+                                               'already exists'), value,
                                              state)
 
-
             if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
-                raise formencode.Invalid(_('Group name may only contain '
-                                           'alphanumeric characters '
-                                           'underscores, periods or dashes '
-                                           'and must begin with alphanumeric '
-                                           'character'), value, state)
+                raise formencode.Invalid(
+                    _('RepoGroup name may only contain  alphanumeric characters '
+                      'underscores, periods or dashes and must begin with '
+                      'alphanumeric character'),
+                    value,
+                    state
+                )
 
     return _ValidUsersGroup
 
@@ -141,15 +149,14 @@
 
             old_gname = None
             if edit:
-                old_gname = Group.get(
-                            old_data.get('group_id')).group_name
+                old_gname = RepoGroup.get(old_data.get('group_id')).group_name
 
             if old_gname != group_name or not edit:
 
                 # check group
-                gr = Group.query()\
-                      .filter(Group.group_name == slug)\
-                      .filter(Group.group_parent_id == group_parent_id)\
+                gr = RepoGroup.query()\
+                      .filter(RepoGroup.group_name == slug)\
+                      .filter(RepoGroup.group_parent_id == group_parent_id)\
                       .scalar()
 
                 if gr:
@@ -173,58 +180,64 @@
 
     return _ValidReposGroup
 
+
 class ValidPassword(formencode.validators.FancyValidator):
 
     def to_python(self, value, state):
 
-        if value:
+        if not value:
+            return
 
-            if value.get('password'):
-                try:
-                    value['password'] = get_crypt_password(value['password'])
-                except UnicodeEncodeError:
-                    e_dict = {'password':_('Invalid characters in password')}
-                    raise formencode.Invalid('', value, state, error_dict=e_dict)
+        if value.get('password'):
+            try:
+                value['password'] = get_crypt_password(value['password'])
+            except UnicodeEncodeError:
+                e_dict = {'password': _('Invalid characters in password')}
+                raise formencode.Invalid('', value, state, error_dict=e_dict)
 
-            if value.get('password_confirmation'):
-                try:
-                    value['password_confirmation'] = \
-                        get_crypt_password(value['password_confirmation'])
-                except UnicodeEncodeError:
-                    e_dict = {'password_confirmation':_('Invalid characters in password')}
-                    raise formencode.Invalid('', value, state, error_dict=e_dict)
+        if value.get('password_confirmation'):
+            try:
+                value['password_confirmation'] = \
+                    get_crypt_password(value['password_confirmation'])
+            except UnicodeEncodeError:
+                e_dict = {
+                    'password_confirmation': _('Invalid characters in password')
+                }
+                raise formencode.Invalid('', value, state, error_dict=e_dict)
 
-            if value.get('new_password'):
-                try:
-                    value['new_password'] = \
-                        get_crypt_password(value['new_password'])
-                except UnicodeEncodeError:
-                    e_dict = {'new_password':_('Invalid characters in password')}
-                    raise formencode.Invalid('', value, state, error_dict=e_dict)
+        if value.get('new_password'):
+            try:
+                value['new_password'] = \
+                    get_crypt_password(value['new_password'])
+            except UnicodeEncodeError:
+                e_dict = {'new_password': _('Invalid characters in password')}
+                raise formencode.Invalid('', value, state, error_dict=e_dict)
 
-            return value
+        return value
+
 
 class ValidPasswordsMatch(formencode.validators.FancyValidator):
 
     def validate_python(self, value, state):
-        
+
         pass_val = value.get('password') or value.get('new_password')
         if pass_val != value['password_confirmation']:
             e_dict = {'password_confirmation':
                    _('Passwords do not match')}
             raise formencode.Invalid('', value, state, error_dict=e_dict)
 
+
 class ValidAuth(formencode.validators.FancyValidator):
     messages = {
         'invalid_password':_('invalid password'),
         'invalid_login':_('invalid user name'),
         'disabled_account':_('Your account is disabled')
     }
-    
+
     # error mapping
-    e_dict = {'username':messages['invalid_login'],
-              'password':messages['invalid_password']}
-    e_dict_disable = {'username':messages['disabled_account']}
+    e_dict = {'username': messages['invalid_login'],
+              'password': messages['invalid_password']}
+    e_dict_disable = {'username': messages['disabled_account']}
 
     def validate_python(self, value, state):
         password = value['password']
@@ -235,16 +248,21 @@
             return value
         else:
             if user and user.active is False:
-                log.warning('user %s is disabled', username)
-                raise formencode.Invalid(self.message('disabled_account',
-                                         state=State_obj),
-                                         value, state,
-                                         error_dict=self.e_dict_disable)
+                log.warning('user %s is disabled' % username)
+                raise formencode.Invalid(
+                    self.message('disabled_account',
+                    state=State_obj),
+                    value, state,
+                    error_dict=self.e_dict_disable
+                )
             else:
-                log.warning('user %s not authenticated', username)
-                raise formencode.Invalid(self.message('invalid_password',
-                                         state=State_obj), value, state,
-                                         error_dict=self.e_dict)
+                log.warning('user %s failed to authenticate' % username)
+                raise formencode.Invalid(
+                    self.message('invalid_password',
+                    state=State_obj), value, state,
+                    error_dict=self.e_dict
+                )
+
 
 class ValidRepoUser(formencode.validators.FancyValidator):
 
@@ -257,6 +275,7 @@
                                      value, state)
         return value
 
+
 def ValidRepoName(edit, old_data):
     class _ValidRepoName(formencode.validators.FancyValidator):
         def to_python(self, value, state):
@@ -268,41 +287,41 @@
                 e_dict = {'repo_name': _('This repository name is disallowed')}
                 raise formencode.Invalid('', value, state, error_dict=e_dict)
 
-
             if value.get('repo_group'):
-                gr = Group.get(value.get('repo_group'))
+                gr = RepoGroup.get(value.get('repo_group'))
                 group_path = gr.full_path
                 # value needs to be aware of group name in order to check
                 # db key This is an actual just the name to store in the
                 # database
-                repo_name_full = group_path + Group.url_sep() + repo_name
-                
+                repo_name_full = group_path + RepoGroup.url_sep() + repo_name
+
             else:
                 group_path = ''
                 repo_name_full = repo_name
 
-
             value['repo_name_full'] = repo_name_full
             rename = old_data.get('repo_name') != repo_name_full
             create = not edit
             if  rename or create:
 
                 if group_path != '':
-                    if RepoModel().get_by_repo_name(repo_name_full,):
-                        e_dict = {'repo_name':_('This repository already '
-                                                'exists in a group "%s"') %
-                                  gr.group_name}
+                    if Repository.get_by_repo_name(repo_name_full):
+                        e_dict = {
+                            'repo_name': _('This repository already exists in '
+                                           'a group "%s"') % gr.group_name
+                        }
                         raise formencode.Invalid('', value, state,
                                                  error_dict=e_dict)
-                elif Group.get_by_group_name(repo_name_full):
-                        e_dict = {'repo_name':_('There is a group with this'
-                                                ' name already "%s"') %
-                                  repo_name_full}
+                elif RepoGroup.get_by_group_name(repo_name_full):
+                        e_dict = {
+                            'repo_name': _('There is a group with this name '
+                                           'already "%s"') % repo_name_full
+                        }
                         raise formencode.Invalid('', value, state,
                                                  error_dict=e_dict)
 
-                elif RepoModel().get_by_repo_name(repo_name_full):
-                        e_dict = {'repo_name':_('This repository '
+                elif Repository.get_by_repo_name(repo_name_full):
+                        e_dict = {'repo_name': _('This repository '
                                                 'already exists')}
                         raise formencode.Invalid('', value, state,
                                                  error_dict=e_dict)
@@ -311,24 +330,9 @@
 
     return _ValidRepoName
 
-def ValidForkName():
-    class _ValidForkName(formencode.validators.FancyValidator):
-        def to_python(self, value, state):
 
-            repo_name = value.get('fork_name')
-
-            slug = repo_name_slug(repo_name)
-            if slug in [ADMIN_PREFIX, '']:
-                e_dict = {'repo_name': _('This repository name is disallowed')}
-                raise formencode.Invalid('', value, state, error_dict=e_dict)
-
-            if RepoModel().get_by_repo_name(repo_name):
-                e_dict = {'fork_name':_('This repository '
-                                        'already exists')}
-                raise formencode.Invalid('', value, state,
-                                         error_dict=e_dict)
-            return value
-    return _ValidForkName
+def ValidForkName(*args, **kwargs):
+    return ValidRepoName(*args, **kwargs)
 
 
 def SlugifyName():
@@ -339,6 +343,7 @@
 
     return _SlugifyName
 
+
 def ValidCloneUri():
     from mercurial.httprepo import httprepository, httpsrepository
     from rhodecode.lib.utils import make_ui
@@ -351,14 +356,14 @@
             elif value.startswith('https'):
                 try:
                     httpsrepository(make_ui('db'), value).capabilities
-                except Exception, e:
+                except Exception:
                     log.error(traceback.format_exc())
                     raise formencode.Invalid(_('invalid clone url'), value,
                                              state)
             elif value.startswith('http'):
                 try:
                     httprepository(make_ui('db'), value).capabilities
-                except Exception, e:
+                except Exception:
                     log.error(traceback.format_exc())
                     raise formencode.Invalid(_('invalid clone url'), value,
                                              state)
@@ -370,6 +375,7 @@
 
     return _ValidCloneUri
 
+
 def ValidForkType(old_data):
     class _ValidForkType(formencode.validators.FancyValidator):
 
@@ -381,64 +387,77 @@
             return value
     return _ValidForkType
 
-class ValidPerms(formencode.validators.FancyValidator):
-    messages = {'perm_new_member_name':_('This username or users group name'
-                                         ' is not valid')}
+
+def ValidPerms(type_='repo'):
+    if type_ == 'group':
+        EMPTY_PERM = 'group.none'
+    elif type_ == 'repo':
+        EMPTY_PERM = 'repository.none'
 
-    def to_python(self, value, state):
-        perms_update = []
-        perms_new = []
-        #build a list of permission to update and new permission to create
-        for k, v in value.items():
-            #means new added member to permissions
-            if k.startswith('perm_new_member'):
-                new_perm = value.get('perm_new_member', False)
-                new_member = value.get('perm_new_member_name', False)
-                new_type = value.get('perm_new_member_type')
+    class _ValidPerms(formencode.validators.FancyValidator):
+        messages = {
+            'perm_new_member_name':
+                _('This username or users group name is not valid')
+        }
+
+        def to_python(self, value, state):
+            perms_update = []
+            perms_new = []
+            # build a list of permission to update and new permission to create
+            for k, v in value.items():
+                # means new added member to permissions
+                if k.startswith('perm_new_member'):
+                    new_perm = value.get('perm_new_member', False)
+                    new_member = value.get('perm_new_member_name', False)
+                    new_type = value.get('perm_new_member_type')
 
-                if new_member and new_perm:
-                    if (new_member, new_perm, new_type) not in perms_new:
-                        perms_new.append((new_member, new_perm, new_type))
-            elif k.startswith('u_perm_') or k.startswith('g_perm_'):
-                member = k[7:]
-                t = {'u':'user',
-                     'g':'users_group'}[k[0]]
-                if member == 'default':
-                    if value['private']:
-                        #set none for default when updating to private repo
-                        v = 'repository.none'
-                perms_update.append((member, v, t))
+                    if new_member and new_perm:
+                        if (new_member, new_perm, new_type) not in perms_new:
+                            perms_new.append((new_member, new_perm, new_type))
+                elif k.startswith('u_perm_') or k.startswith('g_perm_'):
+                    member = k[7:]
+                    t = {'u': 'user',
+                         'g': 'users_group'
+                    }[k[0]]
+                    if member == 'default':
+                        if value.get('private'):
+                            # set none for default when updating to private repo
+                            v = EMPTY_PERM
+                    perms_update.append((member, v, t))
 
-        value['perms_updates'] = perms_update
-        value['perms_new'] = perms_new
+            value['perms_updates'] = perms_update
+            value['perms_new'] = perms_new
 
-        #update permissions
-        for k, v, t in perms_new:
-            try:
-                if t is 'user':
-                    self.user_db = User.query()\
-                        .filter(User.active == True)\
-                        .filter(User.username == k).one()
-                if t is 'users_group':
-                    self.user_db = UsersGroup.query()\
-                        .filter(UsersGroup.users_group_active == True)\
-                        .filter(UsersGroup.users_group_name == k).one()
+            # update permissions
+            for k, v, t in perms_new:
+                try:
+                    if t is 'user':
+                        self.user_db = User.query()\
+                            .filter(User.active == True)\
+                            .filter(User.username == k).one()
+                    if t is 'users_group':
+                        self.user_db = UsersGroup.query()\
+                            .filter(UsersGroup.users_group_active == True)\
+                            .filter(UsersGroup.users_group_name == k).one()
 
-            except Exception:
-                msg = self.message('perm_new_member_name',
-                                     state=State_obj)
-                raise formencode.Invalid(msg, value, state,
-                                         error_dict={'perm_new_member_name':msg})
-        return value
+                except Exception:
+                    msg = self.message('perm_new_member_name',
+                                         state=State_obj)
+                    raise formencode.Invalid(
+                        msg, value, state, error_dict={'perm_new_member_name': msg}
+                    )
+            return value
+    return _ValidPerms
+
 
 class ValidSettings(formencode.validators.FancyValidator):
 
     def to_python(self, value, state):
-        #settings  form can't edit user
-        if value.has_key('user'):
+        # settings  form can't edit user
+        if 'user' in value:
             del['value']['user']
+        return value
 
-        return value
 
 class ValidPath(formencode.validators.FancyValidator):
     def to_python(self, value, state):
@@ -446,33 +465,37 @@
         if not os.path.isdir(value):
             msg = _('This is not a valid path')
             raise formencode.Invalid(msg, value, state,
-                                     error_dict={'paths_root_path':msg})
+                                     error_dict={'paths_root_path': msg})
         return value
 
+
 def UniqSystemEmail(old_data):
     class _UniqSystemEmail(formencode.validators.FancyValidator):
         def to_python(self, value, state):
             value = value.lower()
-            if old_data.get('email') != value:
-                user = User.query().filter(User.email == value).scalar()
+            if old_data.get('email', '').lower() != value:
+                user = User.get_by_email(value, case_insensitive=True)
                 if user:
                     raise formencode.Invalid(
-                                    _("This e-mail address is already taken"),
-                                    value, state)
+                        _("This e-mail address is already taken"), value, state
+                    )
             return value
 
     return _UniqSystemEmail
 
+
 class ValidSystemEmail(formencode.validators.FancyValidator):
     def to_python(self, value, state):
         value = value.lower()
-        user = User.query().filter(User.email == value).scalar()
+        user = User.get_by_email(value, case_insensitive=True)
         if  user is None:
-            raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
-                                     value, state)
+            raise formencode.Invalid(
+                _("This e-mail address doesn't exist."), value, state
+            )
 
         return value
 
+
 class LdapLibValidator(formencode.validators.FancyValidator):
 
     def to_python(self, value, state):
@@ -483,45 +506,50 @@
             raise LdapImportError
         return value
 
+
 class AttrLoginValidator(formencode.validators.FancyValidator):
 
     def to_python(self, value, state):
 
         if not value or not isinstance(value, (str, unicode)):
-            raise formencode.Invalid(_("The LDAP Login attribute of the CN "
-                                       "must be specified - this is the name "
-                                       "of the attribute that is equivalent "
-                                       "to 'username'"),
-                                     value, state)
+            raise formencode.Invalid(
+                _("The LDAP Login attribute of the CN must be specified - "
+                  "this is the name of the attribute that is equivalent "
+                  "to 'username'"), value, state
+            )
 
         return value
 
-#===============================================================================
+
+#==============================================================================
 # FORMS
-#===============================================================================
+#==============================================================================
 class LoginForm(formencode.Schema):
     allow_extra_fields = True
     filter_extra_fields = True
     username = UnicodeString(
-                             strip=True,
-                             min=1,
-                             not_empty=True,
-                             messages={
-                                'empty':_('Please enter a login'),
-                                'tooShort':_('Enter a value %(min)i characters long or more')}
-                            )
+        strip=True,
+        min=1,
+        not_empty=True,
+        messages={
+           'empty': _('Please enter a login'),
+           'tooShort': _('Enter a value %(min)i characters long or more')}
+    )
 
     password = UnicodeString(
-                            strip=True,
-                            min=3,
-                            not_empty=True,
-                            messages={
-                                'empty':_('Please enter a password'),
-                                'tooShort':_('Enter %(min)i characters or more')}
-                                )
+        strip=True,
+        min=3,
+        not_empty=True,
+        messages={
+            'empty': _('Please enter a password'),
+            'tooShort': _('Enter %(min)i characters or more')}
+    )
+
+    remember = StringBoolean(if_missing=False)
 
     chained_validators = [ValidAuth]
 
+
 def UserForm(edit=False, old_data={}):
     class _UserForm(formencode.Schema):
         allow_extra_fields = True
@@ -530,15 +558,17 @@
                        ValidUsername(edit, old_data))
         if edit:
             new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
-            password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
+            password_confirmation = All(UnicodeString(strip=True, min=6,
+                                                      not_empty=False))
             admin = StringBoolean(if_missing=False)
         else:
             password = All(UnicodeString(strip=True, min=6, not_empty=True))
-            password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
-            
+            password_confirmation = All(UnicodeString(strip=True, min=6,
+                                                      not_empty=False))
+
         active = StringBoolean(if_missing=False)
-        name = UnicodeString(strip=True, min=1, not_empty=True)
-        lastname = UnicodeString(strip=True, min=1, not_empty=True)
+        name = UnicodeString(strip=True, min=1, not_empty=False)
+        lastname = UnicodeString(strip=True, min=1, not_empty=False)
         email = All(Email(not_empty=True), UniqSystemEmail(old_data))
 
         chained_validators = [ValidPasswordsMatch, ValidPassword]
@@ -563,10 +593,11 @@
 
     return _UsersGroupForm
 
+
 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
     class _ReposGroupForm(formencode.Schema):
         allow_extra_fields = True
-        filter_extra_fields = True
+        filter_extra_fields = False
 
         group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
                                SlugifyName())
@@ -576,10 +607,11 @@
                                         testValueList=True,
                                         if_missing=None, not_empty=False)
 
-        chained_validators = [ValidReposGroup(edit, old_data)]
+        chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')]
 
     return _ReposGroupForm
 
+
 def RegisterForm(edit=False, old_data={}):
     class _RegisterForm(formencode.Schema):
         allow_extra_fields = True
@@ -589,14 +621,15 @@
         password = All(UnicodeString(strip=True, min=6, not_empty=True))
         password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
         active = StringBoolean(if_missing=False)
-        name = UnicodeString(strip=True, min=1, not_empty=True)
-        lastname = UnicodeString(strip=True, min=1, not_empty=True)
+        name = UnicodeString(strip=True, min=1, not_empty=False)
+        lastname = UnicodeString(strip=True, min=1, not_empty=False)
         email = All(Email(not_empty=True), UniqSystemEmail(old_data))
 
         chained_validators = [ValidPasswordsMatch, ValidPassword]
 
     return _RegisterForm
 
+
 def PasswordResetForm():
     class _PasswordResetForm(formencode.Schema):
         allow_extra_fields = True
@@ -604,6 +637,7 @@
         email = All(ValidSystemEmail(), Email(not_empty=True))
     return _PasswordResetForm
 
+
 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
              repo_groups=[]):
     class _RepoForm(formencode.Schema):
@@ -624,23 +658,29 @@
             #this is repo owner
             user = All(UnicodeString(not_empty=True), ValidRepoUser)
 
-        chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
+        chained_validators = [ValidRepoName(edit, old_data), ValidPerms()]
     return _RepoForm
 
-def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
+
+def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
+                 repo_groups=[]):
     class _RepoForkForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = False
-        fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
+        repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
                         SlugifyName())
+        repo_group = OneOf(repo_groups, hideList=True)
+        repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
         description = UnicodeString(strip=True, min=1, not_empty=True)
         private = StringBoolean(if_missing=False)
-        repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
-
-        chained_validators = [ValidForkName()]
+        copy_permissions = StringBoolean(if_missing=False)
+        update_after_clone = StringBoolean(if_missing=False)
+        fork_parent_id = UnicodeString()
+        chained_validators = [ValidForkName(edit, old_data)]
 
     return _RepoForkForm
 
+
 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
                      repo_groups=[]):
     class _RepoForm(formencode.Schema):
@@ -652,7 +692,7 @@
         repo_group = OneOf(repo_groups, hideList=True)
         private = StringBoolean(if_missing=False)
 
-        chained_validators = [ValidRepoName(edit, old_data), ValidPerms, 
+        chained_validators = [ValidRepoName(edit, old_data), ValidPerms(),
                               ValidSettings]
     return _RepoForm
 
@@ -667,6 +707,7 @@
 
     return _ApplicationSettingsForm
 
+
 def ApplicationUiSettingsForm():
     class _ApplicationUiSettingsForm(formencode.Schema):
         allow_extra_fields = True
@@ -680,6 +721,7 @@
 
     return _ApplicationUiSettingsForm
 
+
 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
     class _DefaultPermissionsForm(formencode.Schema):
         allow_extra_fields = True
--- a/rhodecode/model/meta.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/model/meta.py	Sun Feb 26 17:25:09 2012 +0200
@@ -3,7 +3,7 @@
 from sqlalchemy.orm import scoped_session, sessionmaker
 from beaker import cache
 
-from rhodecode.model import caching_query
+from rhodecode.lib import caching_query
 
 
 # Beaker CacheManager.  A home base for cache configurations.
@@ -15,7 +15,8 @@
 #
 Session = scoped_session(
                 sessionmaker(
-                    query_cls=caching_query.query_callable(cache_manager)
+                    query_cls=caching_query.query_callable(cache_manager),
+                    expire_on_commit=True,
                 )
           )
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/model/notification.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.model.notification
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Model for notifications
+
+
+    :created_on: Nov 20, 2011
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+import traceback
+import datetime
+
+from pylons.i18n.translation import _
+
+import rhodecode
+from rhodecode.lib import helpers as h
+from rhodecode.model import BaseModel
+from rhodecode.model.db import Notification, User, UserNotification
+
+log = logging.getLogger(__name__)
+
+
+class NotificationModel(BaseModel):
+
+    def __get_user(self, user):
+        return self._get_instance(User, user, callback=User.get_by_username)
+
+    def __get_notification(self, notification):
+        if isinstance(notification, Notification):
+            return notification
+        elif isinstance(notification, int):
+            return Notification.get(notification)
+        else:
+            if notification:
+                raise Exception('notification must be int or Instance'
+                                ' of Notification got %s' % type(notification))
+
+    def create(self, created_by, subject, body, recipients=None,
+               type_=Notification.TYPE_MESSAGE, with_email=True,
+               email_kwargs={}):
+        """
+
+        Creates notification of given type
+
+        :param created_by: int, str or User instance. User who created this
+            notification
+        :param subject:
+        :param body:
+        :param recipients: list of int, str or User objects, when None
+            is given send to all admins
+        :param type_: type of notification
+        :param with_email: send email with this notification
+        :param email_kwargs: additional dict to pass as args to email template
+        """
+        from rhodecode.lib.celerylib import tasks, run_task
+
+        if recipients and not getattr(recipients, '__iter__', False):
+            raise Exception('recipients must be a list of iterable')
+
+        created_by_obj = self.__get_user(created_by)
+
+        if recipients:
+            recipients_objs = []
+            for u in recipients:
+                obj = self.__get_user(u)
+                if obj:
+                    recipients_objs.append(obj)
+            recipients_objs = set(recipients_objs)
+        else:
+            # empty recipients means to all admins
+            recipients_objs = User.query().filter(User.admin == True).all()
+
+        notif = Notification.create(created_by=created_by_obj, subject=subject,
+                                    body=body, recipients=recipients_objs,
+                                    type_=type_)
+
+        if with_email is False:
+            return notif
+
+        # send email with notification
+        for rec in recipients_objs:
+            email_subject = NotificationModel().make_description(notif, False)
+            type_ = type_
+            email_body = body
+            kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
+            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)
+
+        return notif
+
+    def delete(self, user, notification):
+        # we don't want to remove actual notification just the assignment
+        try:
+            notification = self.__get_notification(notification)
+            user = self.__get_user(user)
+            if notification and user:
+                obj = UserNotification.query()\
+                        .filter(UserNotification.user == user)\
+                        .filter(UserNotification.notification
+                                == notification)\
+                        .one()
+                self.sa.delete(obj)
+                return True
+        except Exception:
+            log.error(traceback.format_exc())
+            raise
+
+    def get_for_user(self, user):
+        user = self.__get_user(user)
+        return user.notifications
+
+    def mark_all_read_for_user(self, user):
+        user = self.__get_user(user)
+        UserNotification.query()\
+            .filter(UserNotification.read==False)\
+            .update({'read': True})
+
+    def get_unread_cnt_for_user(self, user):
+        user = self.__get_user(user)
+        return UserNotification.query()\
+                .filter(UserNotification.read == False)\
+                .filter(UserNotification.user == user).count()
+
+    def get_unread_for_user(self, user):
+        user = self.__get_user(user)
+        return [x.notification for x in UserNotification.query()\
+                .filter(UserNotification.read == False)\
+                .filter(UserNotification.user == user).all()]
+
+    def get_user_notification(self, user, notification):
+        user = self.__get_user(user)
+        notification = self.__get_notification(notification)
+
+        return UserNotification.query()\
+            .filter(UserNotification.notification == notification)\
+            .filter(UserNotification.user == user).scalar()
+
+    def make_description(self, notification, show_age=True):
+        """
+        Creates a human readable description based on properties
+        of notification object
+        """
+
+        _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
+                notification.TYPE_MESSAGE:_('sent message'),
+                notification.TYPE_MENTION:_('mentioned you'),
+                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)
+        return tmpl % data
+
+
+class EmailNotificationModel(BaseModel):
+
+    TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
+    TYPE_PASSWORD_RESET = 'passoword_link'
+    TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
+    TYPE_DEFAULT = 'default'
+
+    def __init__(self):
+        self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
+        self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
+
+        self.email_types = {
+            self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
+            self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
+            self.TYPE_REGISTRATION:'email_templates/registration.html',
+            self.TYPE_DEFAULT:'email_templates/default.html'
+        }
+
+    def get_email_tmpl(self, type_, **kwargs):
+        """
+        return generated template for email based on given type
+
+        :param type_:
+        """
+
+        base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
+        email_template = self._tmpl_lookup.get_template(base)
+        # translator inject
+        _kwargs = {'_':_}
+        _kwargs.update(kwargs)
+        log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
+        return email_template.render(**_kwargs)
--- a/rhodecode/model/permission.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/model/permission.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Aug 20, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -28,19 +28,22 @@
 
 from sqlalchemy.exc import DatabaseError
 
+from rhodecode.lib.caching_query import FromCache
+
 from rhodecode.model import BaseModel
-from rhodecode.model.db import User, Permission, UserToPerm, RepoToPerm
-from rhodecode.model.caching_query import FromCache
+from rhodecode.model.db import User, Permission, UserToPerm, UserRepoToPerm
 
 log = logging.getLogger(__name__)
 
 
 class PermissionModel(BaseModel):
-    """Permissions model for RhodeCode
+    """
+    Permissions model for RhodeCode
     """
 
     def get_permission(self, permission_id, cache=False):
-        """Get's permissions by id
+        """
+        Get's permissions by id
 
         :param permission_id: id of permission to get from database
         :param cache: use Cache for this query
@@ -52,7 +55,8 @@
         return perm.get(permission_id)
 
     def get_permission_by_name(self, name, cache=False):
-        """Get's permissions by given name
+        """
+        Get's permissions by given name
 
         :param name: name to fetch
         :param cache: Use cache for this query
@@ -66,8 +70,8 @@
 
     def update(self, form_result):
         perm_user = self.sa.query(User)\
-                .filter(User.username ==
-                        form_result['perm_user_name']).scalar()
+                        .filter(User.username ==
+                                form_result['perm_user_name']).scalar()
         u2p = self.sa.query(UserToPerm).filter(UserToPerm.user ==
                                                perm_user).all()
         if len(u2p) != 3:
@@ -76,7 +80,7 @@
                             ' your database' % len(u2p))
 
         try:
-            #stage 1 change defaults
+            # stage 1 change defaults
             for p in u2p:
                 if p.permission.permission_name.startswith('repository.'):
                     p.permission = self.get_permission_by_name(
@@ -95,19 +99,17 @@
 
             #stage 2 update all default permissions for repos if checked
             if form_result['overwrite_default'] == True:
-                for r2p in self.sa.query(RepoToPerm)\
-                               .filter(RepoToPerm.user == perm_user).all():
+                for r2p in self.sa.query(UserRepoToPerm)\
+                               .filter(UserRepoToPerm.user == perm_user).all():
                     r2p.permission = self.get_permission_by_name(
                                          form_result['default_perm'])
                     self.sa.add(r2p)
 
-            #stage 3 set anonymous access
+            # stage 3 set anonymous access
             if perm_user.username == 'default':
                 perm_user.active = bool(form_result['anonymous'])
                 self.sa.add(perm_user)
 
-            self.sa.commit()
         except (DatabaseError,):
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
--- a/rhodecode/model/repo.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/model/repo.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Jun 5, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -28,24 +28,46 @@
 import traceback
 from datetime import datetime
 
-from vcs.utils.lazy import LazyProperty
-from vcs.backends import get_backend
+from rhodecode.lib.vcs.backends import get_backend
 
+from rhodecode.lib import LazyProperty
 from rhodecode.lib import 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.caching_query import FromCache
-from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
-    Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
+from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
+    Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
+
 
 log = logging.getLogger(__name__)
 
 
 class RepoModel(BaseModel):
 
+    def __get_user(self, user):
+        return self._get_instance(User, user, callback=User.get_by_username)
+
+    def __get_users_group(self, users_group):
+        return self._get_instance(UsersGroup, users_group,
+                                  callback=UsersGroup.get_by_group_name)
+
+    def __get_repos_group(self, repos_group):
+        return self._get_instance(RepoGroup, repos_group,
+                                  callback=RepoGroup.get_by_group_name)
+
+    def __get_repo(self, repository):
+        return self._get_instance(Repository, repository,
+                                  callback=Repository.get_by_repo_name)
+
+    def __get_perm(self, permission):
+        return self._get_instance(Permission, permission,
+                                  callback=Permission.get_by_key)
+
     @LazyProperty
     def repos_path(self):
-        """Get's the repositories root path from database
+        """
+        Get's the repositories root path from database
         """
 
         q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
@@ -60,6 +82,9 @@
                                           "get_repo_%s" % repo_id))
         return repo.scalar()
 
+    def get_repo(self, repository):
+        return self.__get_repo(repository)
+
     def get_by_repo_name(self, repo_name, cache=False):
         repo = self.sa.query(Repository)\
             .filter(Repository.repo_name == repo_name)
@@ -69,7 +94,6 @@
                                           "get_repo_%s" % repo_name))
         return repo.scalar()
 
-
     def get_users_js(self):
 
         users = self.sa.query(User).filter(User.active == True).all()
@@ -93,9 +117,9 @@
 
     def _get_defaults(self, repo_name):
         """
-        Get's information about repository, and returns a dict for 
+        Get's information about repository, and returns a dict for
         usage in forms
-        
+
         :param repo_name:
         """
 
@@ -130,7 +154,6 @@
 
         return defaults
 
-
     def update(self, repo_name, form_data):
         try:
             cur_repo = self.get_by_repo_name(repo_name, cache=False)
@@ -138,48 +161,24 @@
             # update permissions
             for member, perm, member_type in form_data['perms_updates']:
                 if member_type == 'user':
-                    r2p = self.sa.query(RepoToPerm)\
-                            .filter(RepoToPerm.user == User.get_by_username(member))\
-                            .filter(RepoToPerm.repository == cur_repo)\
-                            .one()
-
-                    r2p.permission = self.sa.query(Permission)\
-                                        .filter(Permission.permission_name ==
-                                                perm).scalar()
-                    self.sa.add(r2p)
+                    # this updates existing one
+                    RepoModel().grant_user_permission(
+                        repo=cur_repo, user=member, perm=perm
+                    )
                 else:
-                    g2p = self.sa.query(UsersGroupRepoToPerm)\
-                            .filter(UsersGroupRepoToPerm.users_group ==
-                                    UsersGroup.get_by_group_name(member))\
-                            .filter(UsersGroupRepoToPerm.repository ==
-                                    cur_repo).one()
-
-                    g2p.permission = self.sa.query(Permission)\
-                                        .filter(Permission.permission_name ==
-                                                perm).scalar()
-                    self.sa.add(g2p)
-
+                    RepoModel().grant_users_group_permission(
+                        repo=cur_repo, group_name=member, perm=perm
+                    )
             # set new permissions
             for member, perm, member_type in form_data['perms_new']:
                 if member_type == 'user':
-                    r2p = RepoToPerm()
-                    r2p.repository = cur_repo
-                    r2p.user = User.get_by_username(member)
-
-                    r2p.permission = self.sa.query(Permission)\
-                                        .filter(Permission.
-                                                permission_name == perm)\
-                                                .scalar()
-                    self.sa.add(r2p)
+                    RepoModel().grant_user_permission(
+                        repo=cur_repo, user=member, perm=perm
+                    )
                 else:
-                    g2p = UsersGroupRepoToPerm()
-                    g2p.repository = cur_repo
-                    g2p.users_group = UsersGroup.get_by_group_name(member)
-                    g2p.permission = self.sa.query(Permission)\
-                                        .filter(Permission.
-                                                permission_name == perm)\
-                                                .scalar()
-                    self.sa.add(g2p)
+                    RepoModel().grant_users_group_permission(
+                        repo=cur_repo, group_name=member, perm=perm
+                    )
 
             # update current repo
             for k, v in form_data.items():
@@ -188,7 +187,7 @@
                 elif k == 'repo_name':
                     pass
                 elif k == 'repo_group':
-                    cur_repo.group = Group.get(v)
+                    cur_repo.group = RepoGroup.get(v)
 
                 else:
                     setattr(cur_repo, k, v)
@@ -202,133 +201,220 @@
                 # rename repository
                 self.__rename_repo(old=repo_name, new=new_name)
 
-            self.sa.commit()
             return cur_repo
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def create(self, form_data, cur_user, just_db=False, fork=False):
+        from rhodecode.model.scm import ScmModel
 
         try:
             if fork:
-                repo_name = form_data['fork_name']
-                org_name = form_data['repo_name']
-                org_full_name = org_name
+                fork_parent_id = form_data['fork_parent_id']
 
-            else:
-                org_name = repo_name = form_data['repo_name']
-                repo_name_full = form_data['repo_name_full']
+            # repo name is just a name of repository
+            # while repo_name_full is a full qualified name that is combined
+            # with name and path of group
+            repo_name = form_data['repo_name']
+            repo_name_full = form_data['repo_name_full']
 
             new_repo = Repository()
             new_repo.enable_statistics = False
+
             for k, v in form_data.items():
                 if k == 'repo_name':
-                    if fork:
-                        v = repo_name
-                    else:
-                        v = repo_name_full
+                    v = repo_name_full
                 if k == 'repo_group':
                     k = 'group_id'
-
                 if k == 'description':
-                    v = safe_unicode(v) or repo_name
+                    v = v or repo_name
 
                 setattr(new_repo, k, v)
 
             if fork:
-                parent_repo = self.sa.query(Repository)\
-                        .filter(Repository.repo_name == org_full_name).one()
+                parent_repo = Repository.get(fork_parent_id)
                 new_repo.fork = parent_repo
 
             new_repo.user_id = cur_user.user_id
             self.sa.add(new_repo)
 
-            #create default permission
-            repo_to_perm = RepoToPerm()
-            default = 'repository.read'
-            for p in User.get_by_username('default').user_perms:
-                if p.permission.permission_name.startswith('repository.'):
-                    default = p.permission.permission_name
-                    break
+            def _create_default_perms():
+                # create default permission
+                repo_to_perm = UserRepoToPerm()
+                default = 'repository.read'
+                for p in User.get_by_username('default').user_perms:
+                    if p.permission.permission_name.startswith('repository.'):
+                        default = p.permission.permission_name
+                        break
+
+                default_perm = 'repository.none' if form_data['private'] else default
+
+                repo_to_perm.permission_id = self.sa.query(Permission)\
+                        .filter(Permission.permission_name == default_perm)\
+                        .one().permission_id
+
+                repo_to_perm.repository = new_repo
+                repo_to_perm.user_id = User.get_by_username('default').user_id
+
+                self.sa.add(repo_to_perm)
 
-            default_perm = 'repository.none' if form_data['private'] else default
+            if fork:
+                if form_data.get('copy_permissions'):
+                    repo = Repository.get(fork_parent_id)
+                    user_perms = UserRepoToPerm.query()\
+                        .filter(UserRepoToPerm.repository == repo).all()
+                    group_perms = UsersGroupRepoToPerm.query()\
+                        .filter(UsersGroupRepoToPerm.repository == repo).all()
 
-            repo_to_perm.permission_id = self.sa.query(Permission)\
-                    .filter(Permission.permission_name == default_perm)\
-                    .one().permission_id
+                    for perm in user_perms:
+                        UserRepoToPerm.create(perm.user, new_repo,
+                                              perm.permission)
 
-            repo_to_perm.repository = new_repo
-            repo_to_perm.user_id = User.get_by_username('default').user_id
-
-            self.sa.add(repo_to_perm)
+                    for perm in group_perms:
+                        UsersGroupRepoToPerm.create(perm.users_group, new_repo,
+                                                    perm.permission)
+                else:
+                    _create_default_perms()
+            else:
+                _create_default_perms()
 
             if not just_db:
                 self.__create_repo(repo_name, form_data['repo_type'],
                                    form_data['repo_group'],
                                    form_data['clone_uri'])
 
-            self.sa.commit()
-
-            #now automatically start following this repository as owner
-            from rhodecode.model.scm import ScmModel
+            # now automatically start following this repository as owner
             ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
-                                             cur_user.user_id)
+                                                    cur_user.user_id)
+            log_create_repository(new_repo.get_dict(),
+                                  created_by=cur_user.username)
             return new_repo
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def create_fork(self, form_data, cur_user):
+        """
+        Simple wrapper into executing celery task for fork creation
+
+        :param form_data:
+        :param cur_user:
+        """
         from rhodecode.lib.celerylib import tasks, run_task
         run_task(tasks.create_repo_fork, form_data, cur_user)
 
     def delete(self, repo):
+        repo = self.__get_repo(repo)
         try:
             self.sa.delete(repo)
             self.__delete_repo(repo)
-            self.sa.commit()
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
-            raise
-
-    def delete_perm_user(self, form_data, repo_name):
-        try:
-            self.sa.query(RepoToPerm)\
-                .filter(RepoToPerm.repository \
-                        == self.get_by_repo_name(repo_name))\
-                .filter(RepoToPerm.user_id == form_data['user_id']).delete()
-            self.sa.commit()
-        except:
-            log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
-    def delete_perm_users_group(self, form_data, repo_name):
+    def grant_user_permission(self, repo, user, perm):
+        """
+        Grant permission for user on given repository, or update existing one
+        if found
+
+        :param repo: Instance of Repository, repository_id, or repository name
+        :param user: Instance of User, user_id or username
+        :param perm: Instance of Permission, or permission_name
+        """
+        user = self.__get_user(user)
+        repo = self.__get_repo(repo)
+        permission = self.__get_perm(perm)
+
+        # check if we have that permission already
+        obj = self.sa.query(UserRepoToPerm)\
+            .filter(UserRepoToPerm.user == user)\
+            .filter(UserRepoToPerm.repository == repo)\
+            .scalar()
+        if obj is None:
+            # create new !
+            obj = UserRepoToPerm()
+        obj.repository = repo
+        obj.user = user
+        obj.permission = permission
+        self.sa.add(obj)
+
+    def revoke_user_permission(self, repo, user):
+        """
+        Revoke permission for user on given repository
+
+        :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)
+
+        obj = self.sa.query(UserRepoToPerm)\
+            .filter(UserRepoToPerm.repository == repo)\
+            .filter(UserRepoToPerm.user == user)\
+            .one()
+        self.sa.delete(obj)
+
+    def grant_users_group_permission(self, repo, group_name, perm):
+        """
+        Grant permission for users group on given repository, or update
+        existing one if found
+
+        :param repo: Instance of Repository, repository_id, or repository name
+        :param group_name: Instance of UserGroup, users_group_id,
+            or users group name
+        :param perm: Instance of Permission, or permission_name
+        """
+        repo = self.__get_repo(repo)
+        group_name = self.__get_users_group(group_name)
+        permission = self.__get_perm(perm)
+
+        # check if we have that permission already
+        obj = self.sa.query(UsersGroupRepoToPerm)\
+            .filter(UsersGroupRepoToPerm.users_group == group_name)\
+            .filter(UsersGroupRepoToPerm.repository == repo)\
+            .scalar()
+
+        if obj is None:
+            # create new
+            obj = UsersGroupRepoToPerm()
+
+        obj.repository = repo
+        obj.users_group = group_name
+        obj.permission = permission
+        self.sa.add(obj)
+
+    def revoke_users_group_permission(self, repo, group_name):
+        """
+        Revoke permission for users group on given repository
+
+        :param repo: Instance of Repository, repository_id, or repository name
+        :param group_name: Instance of UserGroup, users_group_id,
+            or users group name
+        """
+        repo = self.__get_repo(repo)
+        group_name = self.__get_users_group(group_name)
+
+        obj = self.sa.query(UsersGroupRepoToPerm)\
+            .filter(UsersGroupRepoToPerm.repository == repo)\
+            .filter(UsersGroupRepoToPerm.users_group == group_name)\
+            .one()
+        self.sa.delete(obj)
+
+    def delete_stats(self, repo_name):
+        """
+        removes stats for given repo
+
+        :param repo_name:
+        """
         try:
-            self.sa.query(UsersGroupRepoToPerm)\
-                .filter(UsersGroupRepoToPerm.repository \
-                        == self.get_by_repo_name(repo_name))\
-                .filter(UsersGroupRepoToPerm.users_group_id \
-                        == form_data['users_group_id']).delete()
-            self.sa.commit()
+            obj = self.sa.query(Statistics)\
+                    .filter(Statistics.repository ==
+                            self.get_by_repo_name(repo_name))\
+                    .one()
+            self.sa.delete(obj)
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
-            raise
-
-    def delete_stats(self, repo_name):
-        try:
-            self.sa.query(Statistics)\
-                .filter(Statistics.repository == \
-                        self.get_by_repo_name(repo_name)).delete()
-            self.sa.commit()
-        except:
-            log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
@@ -345,15 +431,16 @@
         from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
 
         if new_parent_id:
-            paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
+            paths = RepoGroup.get(new_parent_id)\
+                .full_path.split(RepoGroup.url_sep())
             new_parent_path = os.sep.join(paths)
         else:
             new_parent_path = ''
 
-        repo_path = os.path.join(*map(lambda x:safe_str(x),
+        # we need to make it str for mercurial
+        repo_path = os.path.join(*map(lambda x: safe_str(x),
                                 [self.repos_path, new_parent_path, repo_name]))
 
-
         # check if this path is not a repository
         if is_valid_repo(repo_path, self.repos_path):
             raise Exception('This path %s is a valid repository' % repo_path)
@@ -362,13 +449,14 @@
         if is_valid_repos_group(repo_path, self.repos_path):
             raise Exception('This path %s is a valid group' % repo_path)
 
-        log.info('creating repo %s in %s @ %s', repo_name, repo_path,
-                 clone_uri)
+        log.info('creating repo %s in %s @ %s' % (
+                     repo_name, safe_unicode(repo_path), clone_uri
+                )
+        )
         backend = get_backend(alias)
 
         backend(repo_path, create=True, src_url=clone_uri)
 
-
     def __rename_repo(self, old, new):
         """
         renames repository on filesystem
@@ -376,13 +464,14 @@
         :param old: old name
         :param new: new name
         """
-        log.info('renaming repo from %s to %s', old, new)
+        log.info('renaming repo from %s to %s' % (old, new))
 
         old_path = os.path.join(self.repos_path, old)
         new_path = os.path.join(self.repos_path, new)
         if os.path.isdir(new_path):
-            raise Exception('Was trying to rename to already existing dir %s' \
-            		     % new_path)
+            raise Exception(
+                'Was trying to rename to already existing dir %s' % new_path
+            )
         shutil.move(old_path, new_path)
 
     def __delete_repo(self, repo):
@@ -395,14 +484,12 @@
         :param repo: repo object
         """
         rm_path = os.path.join(self.repos_path, repo.repo_name)
-        log.info("Removing %s", rm_path)
-        #disable hg/git
+        log.info("Removing %s" % (rm_path))
+        # disable hg/git
         alias = repo.repo_type
         shutil.move(os.path.join(rm_path, '.%s' % alias),
                     os.path.join(rm_path, 'rm__.%s' % alias))
-        #disable repo
-        shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
-                                          % (datetime.today()\
-                                             .strftime('%Y%m%d_%H%M%S_%f'),
-                                            repo.repo_name)))
-
+        # disable repo
+        _d = 'rm__%s__%s' % (datetime.now().strftime('%Y%m%d_%H%M%S_%f'),
+                             repo.repo_name)
+        shutil.move(rm_path, os.path.join(self.repos_path, _d))
--- a/rhodecode/model/repo_permission.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/model/repo_permission.py	Sun Feb 26 17:25:09 2012 +0200
@@ -8,7 +8,7 @@
     :created_on: Oct 1, 2011
     :author: nvinot, marcink
     :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -25,18 +25,33 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import logging
-from rhodecode.model.db import BaseModel, RepoToPerm, Permission,\
-    UsersGroupRepoToPerm
-from rhodecode.model.meta import Session
+from rhodecode.model import BaseModel
+from rhodecode.model.db import UserRepoToPerm, UsersGroupRepoToPerm, Permission,\
+    User, Repository
 
 log = logging.getLogger(__name__)
 
 
 class RepositoryPermissionModel(BaseModel):
+
+    def __get_user(self, user):
+        return self._get_instance(User, user, callback=User.get_by_username)
+
+    def __get_repo(self, repository):
+        return self._get_instance(Repository, repository,
+                                  callback=Repository.get_by_repo_name)
+
+    def __get_perm(self, permission):
+        return self._get_instance(Permission, permission,
+                                  callback=Permission.get_by_key)
+
     def get_user_permission(self, repository, user):
-        return RepoToPerm.query() \
-                .filter(RepoToPerm.user == user) \
-                .filter(RepoToPerm.repository == repository) \
+        repository = self.__get_repo(repository)
+        user = self.__get_user(user)
+
+        return UserRepoToPerm.query() \
+                .filter(UserRepoToPerm.user == user) \
+                .filter(UserRepoToPerm.repository == repository) \
                 .scalar()
 
     def update_user_permission(self, repository, user, permission):
@@ -46,18 +61,16 @@
             if not current.permission is permission:
                 current.permission = permission
         else:
-            p = RepoToPerm()
+            p = UserRepoToPerm()
             p.user = user
             p.repository = repository
             p.permission = permission
-            Session.add(p)
-        Session.commit()
+            self.sa.add(p)
 
     def delete_user_permission(self, repository, user):
         current = self.get_user_permission(repository, user)
         if current:
-            Session.delete(current)
-            Session.commit()
+            self.sa.delete(current)
 
     def get_users_group_permission(self, repository, users_group):
         return UsersGroupRepoToPerm.query() \
@@ -78,13 +91,11 @@
             p.repository = repository
             p.permission = permission
             self.sa.add(p)
-        Session.commit()
 
     def delete_users_group_permission(self, repository, users_group):
         current = self.get_users_group_permission(repository, users_group)
         if current:
             self.sa.delete(current)
-        Session.commit()
 
     def update_or_delete_user_permission(self, repository, user, permission):
         if permission:
--- a/rhodecode/model/repos_group.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/model/repos_group.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Jan 25, 2011
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -28,19 +28,32 @@
 import traceback
 import shutil
 
-from pylons.i18n.translation import _
-
-from vcs.utils.lazy import LazyProperty
+from rhodecode.lib import LazyProperty
 
 from rhodecode.model import BaseModel
-from rhodecode.model.caching_query import FromCache
-from rhodecode.model.db import Group, RhodeCodeUi
+from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
+    User, Permission, UsersGroupRepoGroupToPerm, UsersGroup
 
 log = logging.getLogger(__name__)
 
 
 class ReposGroupModel(BaseModel):
 
+    def __get_user(self, user):
+        return self._get_instance(User, user, callback=User.get_by_username)
+
+    def __get_users_group(self, users_group):
+        return self._get_instance(UsersGroup, users_group,
+                                  callback=UsersGroup.get_by_group_name)
+
+    def __get_repos_group(self, repos_group):
+        return self._get_instance(RepoGroup, repos_group,
+                                  callback=RepoGroup.get_by_group_name)
+
+    def __get_perm(self, permission):
+        return self._get_instance(Permission, permission,
+                                  callback=Permission.get_by_key)
+
     @LazyProperty
     def repos_path(self):
         """
@@ -50,6 +63,24 @@
         q = RhodeCodeUi.get_by_key('/').one()
         return q.ui_value
 
+    def _create_default_perms(self, new_group):
+        # create default permission
+        repo_group_to_perm = UserRepoGroupToPerm()
+        default_perm = 'group.read'
+        for p in User.get_by_username('default').user_perms:
+            if p.permission.permission_name.startswith('group.'):
+                default_perm = p.permission.permission_name
+                break
+
+        repo_group_to_perm.permission_id = self.sa.query(Permission)\
+                .filter(Permission.permission_name == default_perm)\
+                .one().permission_id
+
+        repo_group_to_perm.group = new_group
+        repo_group_to_perm.user_id = User.get_by_username('default').user_id
+
+        self.sa.add(repo_group_to_perm)
+
     def __create_group(self, group_name):
         """
         makes repositories group on filesystem
@@ -59,7 +90,7 @@
         """
 
         create_path = os.path.join(self.repos_path, group_name)
-        log.debug('creating new group in %s', create_path)
+        log.debug('creating new group in %s' % create_path)
 
         if os.path.isdir(create_path):
             raise Exception('That directory already exists !')
@@ -69,7 +100,7 @@
     def __rename_group(self, old, new):
         """
         Renames a group on filesystem
-        
+
         :param group_name:
         """
 
@@ -77,13 +108,12 @@
             log.debug('skipping group rename')
             return
 
-        log.debug('renaming repos group from %s to %s', old, new)
-
+        log.debug('renaming repos group from %s to %s' % (old, new))
 
         old_path = os.path.join(self.repos_path, old)
         new_path = os.path.join(self.repos_path, new)
 
-        log.debug('renaming repos paths from %s to %s', old_path, new_path)
+        log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
 
         if os.path.isdir(new_path):
             raise Exception('Was trying to rename to already '
@@ -93,10 +123,10 @@
     def __delete_group(self, group):
         """
         Deletes a group from a filesystem
-        
+
         :param group: instance of group from database
         """
-        paths = group.full_path.split(Group.url_sep())
+        paths = group.full_path.split(RepoGroup.url_sep())
         paths = os.sep.join(paths)
 
         rm_path = os.path.join(self.repos_path, paths)
@@ -104,33 +134,59 @@
             # delete only if that path really exists
             os.rmdir(rm_path)
 
-    def create(self, form_data):
+    def create(self, group_name, group_description, parent, just_db=False):
         try:
-            new_repos_group = Group()
-            new_repos_group.group_description = form_data['group_description']
-            new_repos_group.parent_group = Group.get(form_data['group_parent_id'])
-            new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
+            new_repos_group = RepoGroup()
+            new_repos_group.group_description = group_description
+            new_repos_group.parent_group = self.__get_repos_group(parent)
+            new_repos_group.group_name = new_repos_group.get_new_name(group_name)
 
             self.sa.add(new_repos_group)
+            self._create_default_perms(new_repos_group)
 
-            self.__create_group(new_repos_group.group_name)
+            if not just_db:
+                # we need to flush here, in order to check if database won't
+                # throw any exceptions, create filesystem dirs at the very end
+                self.sa.flush()
+                self.__create_group(new_repos_group.group_name)
 
-            self.sa.commit()
             return new_repos_group
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def update(self, repos_group_id, form_data):
 
         try:
-            repos_group = Group.get(repos_group_id)
+            repos_group = RepoGroup.get(repos_group_id)
+
+            # update permissions
+            for member, perm, member_type in form_data['perms_updates']:
+                if member_type == 'user':
+                    # this updates also current one if found
+                    ReposGroupModel().grant_user_permission(
+                        repos_group=repos_group, user=member, perm=perm
+                    )
+                else:
+                    ReposGroupModel().grant_users_group_permission(
+                        repos_group=repos_group, group_name=member, perm=perm
+                    )
+            # set new permissions
+            for member, perm, member_type in form_data['perms_new']:
+                if member_type == 'user':
+                    ReposGroupModel().grant_user_permission(
+                        repos_group=repos_group, user=member, perm=perm
+                    )
+                else:
+                    ReposGroupModel().grant_users_group_permission(
+                        repos_group=repos_group, group_name=member, perm=perm
+                    )
+
             old_path = repos_group.full_path
-                
+
             # change properties
             repos_group.group_description = form_data['group_description']
-            repos_group.parent_group = Group.get(form_data['group_parent_id'])
+            repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
             repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
 
             new_path = repos_group.full_path
@@ -139,26 +195,116 @@
 
             self.__rename_group(old_path, new_path)
 
-            # we need to get all repositories from this new group and 
+            # we need to get all repositories from this new group and
             # rename them accordingly to new group path
             for r in repos_group.repositories:
                 r.repo_name = r.get_new_name(r.just_name)
                 self.sa.add(r)
 
-            self.sa.commit()
             return repos_group
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def delete(self, users_group_id):
         try:
-            users_group = Group.get(users_group_id)
+            users_group = RepoGroup.get(users_group_id)
             self.sa.delete(users_group)
             self.__delete_group(users_group)
-            self.sa.commit()
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
+
+    def grant_user_permission(self, repos_group, user, perm):
+        """
+        Grant permission for user on given repositories group, or update
+        existing one if found
+
+        :param repos_group: Instance of ReposGroup, repositories_group_id,
+            or repositories_group name
+        :param user: Instance of User, user_id or username
+        :param perm: Instance of Permission, or permission_name
+        """
+
+        repos_group = self.__get_repos_group(repos_group)
+        user = self.__get_user(user)
+        permission = self.__get_perm(perm)
+
+        # check if we have that permission already
+        obj = self.sa.query(UserRepoGroupToPerm)\
+            .filter(UserRepoGroupToPerm.user == user)\
+            .filter(UserRepoGroupToPerm.group == repos_group)\
+            .scalar()
+        if obj is None:
+            # create new !
+            obj = UserRepoGroupToPerm()
+        obj.group = repos_group
+        obj.user = user
+        obj.permission = permission
+        self.sa.add(obj)
+
+    def revoke_user_permission(self, repos_group, user):
+        """
+        Revoke permission for user on given repositories group
+
+        :param repos_group: Instance of ReposGroup, repositories_group_id,
+            or repositories_group name
+        :param user: Instance of User, user_id or username
+        """
+
+        repos_group = self.__get_repos_group(repos_group)
+        user = self.__get_user(user)
+
+        obj = self.sa.query(UserRepoGroupToPerm)\
+            .filter(UserRepoGroupToPerm.user == user)\
+            .filter(UserRepoGroupToPerm.group == repos_group)\
+            .one()
+        self.sa.delete(obj)
+
+    def grant_users_group_permission(self, repos_group, group_name, perm):
+        """
+        Grant permission for users group on given repositories group, or update
+        existing one if found
+
+        :param repos_group: Instance of ReposGroup, repositories_group_id,
+            or repositories_group name
+        :param group_name: Instance of UserGroup, users_group_id,
+            or users group name
+        :param perm: Instance of Permission, or permission_name
+        """
+        repos_group = self.__get_repos_group(repos_group)
+        group_name = self.__get_users_group(group_name)
+        permission = self.__get_perm(perm)
+
+        # check if we have that permission already
+        obj = self.sa.query(UsersGroupRepoGroupToPerm)\
+            .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
+            .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
+            .scalar()
+
+        if obj is None:
+            # create new
+            obj = UsersGroupRepoGroupToPerm()
+
+        obj.group = repos_group
+        obj.users_group = group_name
+        obj.permission = permission
+        self.sa.add(obj)
+
+    def revoke_users_group_permission(self, repos_group, group_name):
+        """
+        Revoke permission for users group on given repositories group
+
+        :param repos_group: Instance of ReposGroup, repositories_group_id,
+            or repositories_group name
+        :param group_name: Instance of UserGroup, users_group_id,
+            or users group name
+        """
+        repos_group = self.__get_repos_group(repos_group)
+        group_name = self.__get_users_group(group_name)
+
+        obj = self.sa.query(UsersGroupRepoGroupToPerm)\
+            .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
+            .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
+            .one()
+        self.sa.delete(obj)
--- a/rhodecode/model/scm.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/model/scm.py	Sun Feb 26 17:25:09 2012 +0200
@@ -28,22 +28,20 @@
 import logging
 import cStringIO
 
-from sqlalchemy.exc import DatabaseError
-
-from vcs import get_backend
-from vcs.exceptions import RepositoryError
-from vcs.utils.lazy import LazyProperty
-from vcs.nodes import FileNode
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.exceptions import RepositoryError
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.vcs.nodes import FileNode
 
 from rhodecode import BACKENDS
 from rhodecode.lib import helpers as h
 from rhodecode.lib import safe_str
-from rhodecode.lib.auth import HasRepoPermissionAny
+from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
     action_logger, EmptyChangeset
 from rhodecode.model import BaseModel
 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
-    UserFollowing, UserLog, User
+    UserFollowing, UserLog, User, RepoGroup
 
 log = logging.getLogger(__name__)
 
@@ -63,6 +61,7 @@
     def __repr__(self):
         return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
 
+
 class CachedRepoList(object):
 
     def __init__(self, db_repo_list, repos_path, order_by=None):
@@ -79,19 +78,18 @@
 
     def __iter__(self):
         for dbr in self.db_repo_list:
-
             scmr = dbr.scm_instance_cached
-
             # check permission at this level
-            if not HasRepoPermissionAny('repository.read', 'repository.write',
-                                        'repository.admin')(dbr.repo_name,
-                                                            'get repo check'):
+            if not HasRepoPermissionAny(
+                'repository.read', 'repository.write', 'repository.admin'
+            )(dbr.repo_name, 'get repo check'):
                 continue
 
             if scmr is None:
-                log.error('%s this repository is present in database but it '
-                          'cannot be created as an scm instance',
-                          dbr.repo_name)
+                log.error(
+                    '%s this repository is present in database but it '
+                    'cannot be created as an scm instance' % dbr.repo_name
+                )
                 continue
 
             last_change = scmr.last_change
@@ -103,8 +101,7 @@
             tmp_d['description'] = dbr.description
             tmp_d['description_sort'] = tmp_d['description']
             tmp_d['last_change'] = last_change
-            tmp_d['last_change_sort'] = time.mktime(last_change \
-                                                    .timetuple())
+            tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
             tmp_d['tip'] = tip.raw_id
             tmp_d['tip_sort'] = tip.revision
             tmp_d['rev'] = tip.revision
@@ -115,17 +112,53 @@
             tmp_d['last_msg'] = tip.message
             tmp_d['author'] = tip.author
             tmp_d['dbrepo'] = dbr.get_dict()
-            tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
-                                                                    else {}
+            tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
             yield tmp_d
 
+
+class GroupList(object):
+
+    def __init__(self, db_repo_group_list):
+        self.db_repo_group_list = db_repo_group_list
+
+    def __len__(self):
+        return len(self.db_repo_group_list)
+
+    def __repr__(self):
+        return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
+
+    def __iter__(self):
+        for dbgr in self.db_repo_group_list:
+            # check permission at this level
+            if not HasReposGroupPermissionAny(
+                'group.read', 'group.write', 'group.admin'
+            )(dbgr.group_name, 'get group repo check'):
+                continue
+
+            yield dbgr
+
+
 class ScmModel(BaseModel):
-    """Generic Scm Model
+    """
+    Generic Scm Model
     """
 
+    def __get_repo(self, instance):
+        cls = Repository
+        if isinstance(instance, cls):
+            return instance
+        elif isinstance(instance, int) or str(instance).isdigit():
+            return cls.get(instance)
+        elif isinstance(instance, basestring):
+            return cls.get_by_repo_name(instance)
+        elif instance:
+            raise Exception('given object must be int, basestr or Instance'
+                            ' of %s got %s' % (type(cls), type(instance)))
+
     @LazyProperty
     def repos_path(self):
-        """Get's the repositories root path from database
+        """
+        Get's the repositories root path from database
         """
 
         q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
@@ -133,7 +166,8 @@
         return q.ui_value
 
     def repo_scan(self, repos_path=None):
-        """Listing of repositories in given path. This path should not be a
+        """
+        Listing of repositories in given path. This path should not be a
         repository itself. Return a dictionary of repository objects
 
         :param repos_path: path to directory containing repositories
@@ -142,19 +176,19 @@
         if repos_path is None:
             repos_path = self.repos_path
 
-        log.info('scanning for repositories in %s', repos_path)
+        log.info('scanning for repositories in %s' % repos_path)
 
         baseui = make_ui('db')
-        repos_list = {}
+        repos = {}
 
         for name, path in get_filesystem_repos(repos_path, recursive=True):
-            
+
             # name need to be decomposed and put back together using the /
             # since this is internal storage separator for rhodecode
             name = Repository.url_sep().join(name.split(os.sep))
-            
+
             try:
-                if name in repos_list:
+                if name in repos:
                     raise RepositoryError('Duplicate repository name %s '
                                           'found in %s' % (name, path))
                 else:
@@ -162,17 +196,14 @@
                     klass = get_backend(path[0])
 
                     if path[0] == 'hg' and path[0] in BACKENDS.keys():
-
-                        # for mercurial we need to have an str path
-                        repos_list[name] = klass(safe_str(path[1]),
-                                                 baseui=baseui)
+                        repos[name] = klass(safe_str(path[1]), baseui=baseui)
 
                     if path[0] == 'git' and path[0] in BACKENDS.keys():
-                        repos_list[name] = klass(path[1])
+                        repos[name] = klass(path[1])
             except OSError:
                 continue
 
-        return repos_list
+        return repos
 
     def get_repos(self, all_repos=None, sort_key=None):
         """
@@ -192,30 +223,22 @@
 
         return repo_iter
 
+    def get_repos_groups(self, all_groups=None):
+        if all_groups is None:
+            all_groups = RepoGroup.query()\
+                .filter(RepoGroup.group_parent_id == None).all()
+        group_iter = GroupList(all_groups)
+
+        return group_iter
+
     def mark_for_invalidation(self, repo_name):
         """Puts cache invalidation task into db for
         further global cache invalidation
 
         :param repo_name: this repo that should invalidation take place
         """
-
-        log.debug('marking %s for invalidation', repo_name)
-        cache = self.sa.query(CacheInvalidation)\
-            .filter(CacheInvalidation.cache_key == repo_name).scalar()
-
-        if cache:
-            # mark this cache as inactive
-            cache.cache_active = False
-        else:
-            log.debug('cache key not found in invalidation db -> creating one')
-            cache = CacheInvalidation(repo_name)
-
-        try:
-            self.sa.add(cache)
-            self.sa.commit()
-        except (DatabaseError,):
-            log.error(traceback.format_exc())
-            self.sa.rollback()
+        CacheInvalidation.set_invalidate(repo_name)
+        CacheInvalidation.set_invalidate(repo_name + "_README")
 
     def toggle_following_repo(self, follow_repo_id, user_id):
 
@@ -224,17 +247,14 @@
             .filter(UserFollowing.user_id == user_id).scalar()
 
         if f is not None:
-
             try:
                 self.sa.delete(f)
-                self.sa.commit()
                 action_logger(UserTemp(user_id),
                               'stopped_following_repo',
                               RepoTemp(follow_repo_id))
                 return
             except:
                 log.error(traceback.format_exc())
-                self.sa.rollback()
                 raise
 
         try:
@@ -242,13 +262,12 @@
             f.user_id = user_id
             f.follows_repo_id = follow_repo_id
             self.sa.add(f)
-            self.sa.commit()
+
             action_logger(UserTemp(user_id),
                           'started_following_repo',
                           RepoTemp(follow_repo_id))
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def toggle_following_user(self, follow_user_id, user_id):
@@ -259,11 +278,9 @@
         if f is not None:
             try:
                 self.sa.delete(f)
-                self.sa.commit()
                 return
             except:
                 log.error(traceback.format_exc())
-                self.sa.rollback()
                 raise
 
         try:
@@ -271,10 +288,8 @@
             f.user_id = user_id
             f.follows_user_id = follow_user_id
             self.sa.add(f)
-            self.sa.commit()
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def is_following_repo(self, repo_name, user_id, cache=False):
@@ -310,6 +325,13 @@
         return self.sa.query(Repository)\
                 .filter(Repository.fork_id == repo_id).count()
 
+    def mark_as_fork(self, repo, fork, user):
+        repo = self.__get_repo(repo)
+        fork = self.__get_repo(fork)
+        repo.fork = fork
+        self.sa.add(repo)
+        return repo
+
     def pull_changes(self, repo_name, username):
         dbrepo = Repository.get_by_repo_name(repo_name)
         clone_uri = dbrepo.clone_uri
@@ -333,13 +355,13 @@
             log.error(traceback.format_exc())
             raise
 
-    def commit_change(self, repo, repo_name, cs, user, author, message, content,
-                      f_path):
+    def commit_change(self, repo, repo_name, cs, user, author, message,
+                      content, f_path):
 
         if repo.alias == 'hg':
-            from vcs.backends.hg import MercurialInMemoryChangeset as IMC
+            from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
         elif repo.alias == 'git':
-            from vcs.backends.git import GitInMemoryChangeset as IMC
+            from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
 
         # decoding here will force that we have proper encoded values
         # in any other case this will throw exceptions and deny commit
@@ -363,9 +385,9 @@
     def create_node(self, repo, repo_name, cs, user, author, message, content,
                       f_path):
         if repo.alias == 'hg':
-            from vcs.backends.hg import MercurialInMemoryChangeset as IMC
+            from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
         elif repo.alias == 'git':
-            from vcs.backends.git import GitInMemoryChangeset as IMC
+            from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
         # decoding here will force that we have proper encoded values
         # in any other case this will throw exceptions and deny commit
 
@@ -400,20 +422,35 @@
 
         self.mark_for_invalidation(repo_name)
 
+    def get_nodes(self, repo_name, revision, root_path='/', flat=True):
+        """
+        recursive walk in root dir and return a set of all path in that dir
+        based on repository walk function
+
+        :param repo_name: name of repository
+        :param revision: revision for which to list nodes
+        :param root_path: root path to list
+        :param flat: return as a list, if False returns a dict with decription
+
+        """
+        _files = list()
+        _dirs = list()
+        try:
+            _repo = self.__get_repo(repo_name)
+            changeset = _repo.scm_instance.get_changeset(revision)
+            root_path = root_path.lstrip('/')
+            for topnode, dirs, files in changeset.walk(root_path):
+                for f in files:
+                    _files.append(f.path if flat else {"name": f.path,
+                                                       "type": "file"})
+                for d in dirs:
+                    _dirs.append(d.path if flat else {"name": d.path,
+                                                      "type": "dir"})
+        except RepositoryError:
+            log.debug(traceback.format_exc())
+            raise
+
+        return _dirs, _files
 
     def get_unread_journal(self):
         return self.sa.query(UserLog).count()
-
-    def _should_invalidate(self, repo_name):
-        """Looks up database for invalidation signals for this repo_name
-
-        :param repo_name:
-        """
-
-        ret = self.sa.query(CacheInvalidation)\
-            .filter(CacheInvalidation.cache_key == repo_name)\
-            .filter(CacheInvalidation.cache_active == False)\
-            .scalar()
-
-        return ret
-
--- a/rhodecode/model/user.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/model/user.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Apr 9, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -26,13 +26,16 @@
 import logging
 import traceback
 
+from pylons import url
 from pylons.i18n.translation import _
 
 from rhodecode.lib import safe_unicode
+from rhodecode.lib.caching_query import FromCache
+
 from rhodecode.model import BaseModel
-from rhodecode.model.caching_query import FromCache
-from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
-    UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
+from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
+    UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
+    Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup
 from rhodecode.lib.exceptions import DefaultUserException, \
     UserOwnsReposException
 
@@ -42,13 +45,28 @@
 
 log = logging.getLogger(__name__)
 
-PERM_WEIGHTS = {'repository.none': 0,
-                'repository.read': 1,
-                'repository.write': 3,
-                'repository.admin': 3}
+
+PERM_WEIGHTS = {
+    'repository.none': 0,
+    'repository.read': 1,
+    'repository.write': 3,
+    'repository.admin': 4,
+    'group.none': 0,
+    'group.read': 1,
+    'group.write': 3,
+    'group.admin': 4,
+}
 
 
 class UserModel(BaseModel):
+
+    def __get_user(self, user):
+        return self._get_instance(User, user, callback=User.get_by_username)
+
+    def __get_perm(self, permission):
+        return self._get_instance(Permission, permission,
+                                  callback=Permission.get_by_key)
+
     def get(self, user_id, cache=False):
         user = self.sa.query(User)
         if cache:
@@ -56,6 +74,9 @@
                                           "get_user_%s" % user_id))
         return user.get(user_id)
 
+    def get_user(self, user):
+        return self.__get_user(user)
+
     def get_by_username(self, username, cache=False, case_insensitive=False):
 
         if case_insensitive:
@@ -69,13 +90,7 @@
         return user.scalar()
 
     def get_by_api_key(self, api_key, cache=False):
-
-        user = self.sa.query(User)\
-                .filter(User.api_key == api_key)
-        if cache:
-            user = user.options(FromCache("sql_cache_short",
-                                          "get_user_%s" % api_key))
-        return user.scalar()
+        return User.get_by_api_key(api_key, cache)
 
     def create(self, form_data):
         try:
@@ -85,18 +100,91 @@
 
             new_user.api_key = generate_api_key(form_data['username'])
             self.sa.add(new_user)
-            self.sa.commit()
             return new_user
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
+    def create_or_update(self, username, password, email, name, lastname,
+                         active=True, admin=False, ldap_dn=None):
+        """
+        Creates a new instance if not found, or updates current one
+
+        :param username:
+        :param password:
+        :param email:
+        :param active:
+        :param name:
+        :param lastname:
+        :param active:
+        :param admin:
+        :param ldap_dn:
+        """
+
+        from rhodecode.lib.auth import get_crypt_password
+
+        log.debug('Checking for %s account in RhodeCode database' % username)
+        user = User.get_by_username(username, case_insensitive=True)
+        if user is None:
+            log.debug('creating new user %s' % username)
+            new_user = User()
+        else:
+            log.debug('updating user %s' % username)
+            new_user = user
+
+        try:
+            new_user.username = username
+            new_user.admin = admin
+            new_user.password = get_crypt_password(password)
+            new_user.api_key = generate_api_key(username)
+            new_user.email = email
+            new_user.active = active
+            new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
+            new_user.name = name
+            new_user.lastname = lastname
+            self.sa.add(new_user)
+            return new_user
+        except (DatabaseError,):
+            log.error(traceback.format_exc())
+            raise
+
+    def create_for_container_auth(self, username, attrs):
+        """
+        Creates the given user if it's not already in the database
+
+        :param username:
+        :param attrs:
+        """
+        if self.get_by_username(username, case_insensitive=True) is None:
+
+            # autogenerate email for container account without one
+            generate_email = lambda usr: '%s@container_auth.account' % usr
+
+            try:
+                new_user = User()
+                new_user.username = username
+                new_user.password = None
+                new_user.api_key = generate_api_key(username)
+                new_user.email = attrs['email']
+                new_user.active = attrs.get('active', True)
+                new_user.name = attrs['name'] or generate_email(username)
+                new_user.lastname = attrs['lastname']
+
+                self.sa.add(new_user)
+                return new_user
+            except (DatabaseError,):
+                log.error(traceback.format_exc())
+                self.sa.rollback()
+                raise
+        log.debug('User %s already exists. Skipping creation of account'
+                  ' for container auth.', username)
+        return None
+
     def create_ldap(self, username, password, user_dn, attrs):
         """
         Checks if user is in database, if not creates this user marked
         as ldap user
-        
+
         :param username:
         :param password:
         :param user_dn:
@@ -105,31 +193,36 @@
         from rhodecode.lib.auth import get_crypt_password
         log.debug('Checking for such ldap account in RhodeCode database')
         if self.get_by_username(username, case_insensitive=True) is None:
+
+            # autogenerate email for ldap account without one
+            generate_email = lambda usr: '%s@ldap.account' % usr
+
             try:
                 new_user = User()
+                username = username.lower()
                 # add ldap account always lowercase
-                new_user.username = username.lower()
+                new_user.username = username
                 new_user.password = get_crypt_password(password)
                 new_user.api_key = generate_api_key(username)
-                new_user.email = attrs['email']
-                new_user.active = True
+                new_user.email = attrs['email'] or generate_email(username)
+                new_user.active = attrs.get('active', True)
                 new_user.ldap_dn = safe_unicode(user_dn)
                 new_user.name = attrs['name']
                 new_user.lastname = attrs['lastname']
 
                 self.sa.add(new_user)
-                self.sa.commit()
-                return True
+                return new_user
             except (DatabaseError,):
                 log.error(traceback.format_exc())
                 self.sa.rollback()
                 raise
         log.debug('this %s user exists skipping creation of ldap account',
                   username)
-        return False
+        return None
 
     def create_registration(self, form_data):
-        from rhodecode.lib.celerylib import tasks, run_task
+        from rhodecode.model.notification import NotificationModel
+
         try:
             new_user = User()
             for k, v in form_data.items():
@@ -137,18 +230,26 @@
                     setattr(new_user, k, v)
 
             self.sa.add(new_user)
-            self.sa.commit()
+            self.sa.flush()
+
+            # notification to admins
+            subject = _('new user registration')
             body = ('New user registration\n'
-                    'username: %s\n'
-                    'email: %s\n')
-            body = body % (form_data['username'], form_data['email'])
+                    '---------------------\n'
+                    '- Username: %s\n'
+                    '- Full Name: %s\n'
+                    '- Email: %s\n')
+            body = body % (new_user.username, new_user.full_name,
+                           new_user.email)
+            edit_url = url('edit_user', id=new_user.user_id, qualified=True)
+            kw = {'registered_user_url': edit_url}
+            NotificationModel().create(created_by=new_user, subject=subject,
+                                       body=body, recipients=None,
+                                       type_=Notification.TYPE_REGISTRATION,
+                                       email_kwargs=kw)
 
-            run_task(tasks.send_email, None,
-                     _('[RhodeCode] New User registration'),
-                     body)
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def update(self, user_id, form_data):
@@ -167,10 +268,8 @@
                     setattr(user, k, v)
 
             self.sa.add(user)
-            self.sa.commit()
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def update_my_account(self, user_id, form_data):
@@ -189,15 +288,14 @@
                         setattr(user, k, v)
 
             self.sa.add(user)
-            self.sa.commit()
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
-    def delete(self, user_id):
+    def delete(self, user):
+        user = self.__get_user(user)
+
         try:
-            user = self.get(user_id, cache=False)
             if user.username == 'default':
                 raise DefaultUserException(
                                 _("You can't remove this user since it's"
@@ -209,10 +307,8 @@
                                                'remove those repositories') \
                                                % user.repositories)
             self.sa.delete(user)
-            self.sa.commit()
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
 
     def reset_password_link(self, data):
@@ -243,16 +339,19 @@
             else:
                 dbuser = self.get(user_id)
 
-            if dbuser is not None:
-                log.debug('filling %s data', dbuser)
+            if dbuser is not None and dbuser.active:
+                log.debug('filling %s data' % dbuser)
                 for k, v in dbuser.get_dict().items():
                     setattr(auth_user, k, v)
+            else:
+                return False
 
         except:
             log.error(traceback.format_exc())
             auth_user.is_authenticated = False
+            return False
 
-        return auth_user
+        return True
 
     def fill_perms(self, user):
         """
@@ -262,98 +361,109 @@
 
         :param user: user instance to fill his perms
         """
-
-        user.permissions['repositories'] = {}
-        user.permissions['global'] = set()
+        RK = 'repositories'
+        GK = 'repositories_groups'
+        GLOBAL = 'global'
+        user.permissions[RK] = {}
+        user.permissions[GK] = {}
+        user.permissions[GLOBAL] = set()
 
         #======================================================================
         # fetch default permissions
         #======================================================================
-        default_user = self.get_by_username('default', cache=True)
+        default_user = User.get_by_username('default', cache=True)
+        default_user_id = default_user.user_id
 
-        default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
-            .join((Repository, RepoToPerm.repository_id ==
-                   Repository.repo_id))\
-            .join((Permission, RepoToPerm.permission_id ==
-                   Permission.permission_id))\
-            .filter(RepoToPerm.user == default_user).all()
+        default_repo_perms = Permission.get_default_perms(default_user_id)
+        default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
 
         if user.is_admin:
             #==================================================================
-            # #admin have all default rights set to admin
+            # admin user have all default rights for repositories
+            # and groups set to admin
             #==================================================================
-            user.permissions['global'].add('hg.admin')
+            user.permissions[GLOBAL].add('hg.admin')
 
-            for perm in default_perms:
+            # repositories
+            for perm in default_repo_perms:
+                r_k = perm.UserRepoToPerm.repository.repo_name
                 p = 'repository.admin'
-                user.permissions['repositories'][perm.RepoToPerm.
-                                                 repository.repo_name] = p
+                user.permissions[RK][r_k] = p
+
+            # repositories groups
+            for perm in default_repo_groups_perms:
+                rg_k = perm.UserRepoGroupToPerm.group.group_name
+                p = 'group.admin'
+                user.permissions[GK][rg_k] = p
 
         else:
             #==================================================================
-            # set default permissions
+            # set default permissions first for repositories and groups
             #==================================================================
             uid = user.user_id
 
-            #default global
+            # default global permissions
             default_global_perms = self.sa.query(UserToPerm)\
-                .filter(UserToPerm.user == default_user)
+                .filter(UserToPerm.user_id == default_user_id)
 
             for perm in default_global_perms:
-                user.permissions['global'].add(perm.permission.permission_name)
+                user.permissions[GLOBAL].add(perm.permission.permission_name)
 
-            #default for repositories
-            for perm in default_perms:
-                if perm.Repository.private and not (perm.Repository.user_id ==
-                                                    uid):
-                    #diself.sable defaults for private repos,
+            # default for repositories
+            for perm in default_repo_perms:
+                r_k = perm.UserRepoToPerm.repository.repo_name
+                if perm.Repository.private and not (perm.Repository.user_id == uid):
+                    # disable defaults for private repos,
                     p = 'repository.none'
                 elif perm.Repository.user_id == uid:
-                    #set admin if owner
+                    # set admin if owner
                     p = 'repository.admin'
                 else:
                     p = perm.Permission.permission_name
 
-                user.permissions['repositories'][perm.RepoToPerm.
-                                                 repository.repo_name] = p
+                user.permissions[RK][r_k] = p
+
+            # default for repositories groups
+            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
             #==================================================================
 
-            #user global
+            # user global
             user_perms = self.sa.query(UserToPerm)\
                     .options(joinedload(UserToPerm.permission))\
                     .filter(UserToPerm.user_id == uid).all()
 
             for perm in user_perms:
-                user.permissions['global'].add(perm.permission.
-                                               permission_name)
+                user.permissions[GLOBAL].add(perm.permission.permission_name)
 
-            #user repositories
-            user_repo_perms = self.sa.query(RepoToPerm, Permission,
-                                            Repository)\
-                .join((Repository, RepoToPerm.repository_id ==
-                       Repository.repo_id))\
-                .join((Permission, RepoToPerm.permission_id ==
-                       Permission.permission_id))\
-                .filter(RepoToPerm.user_id == uid).all()
+            # user repositories
+            user_repo_perms = \
+             self.sa.query(UserRepoToPerm, Permission, Repository)\
+             .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
+             .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
+             .filter(UserRepoToPerm.user_id == uid)\
+             .all()
 
             for perm in user_repo_perms:
                 # set admin if owner
+                r_k = perm.UserRepoToPerm.repository.repo_name
                 if perm.Repository.user_id == uid:
                     p = 'repository.admin'
                 else:
                     p = perm.Permission.permission_name
-                user.permissions['repositories'][perm.RepoToPerm.
-                                                 repository.repo_name] = p
+                user.permissions[RK][r_k] = p
 
             #==================================================================
             # check if user is part of groups for this repository and fill in
             # (or replace with higher) permissions
             #==================================================================
 
-            #users group global
+            # users group global
             user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
                 .options(joinedload(UsersGroupToPerm.permission))\
                 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
@@ -361,30 +471,82 @@
                 .filter(UsersGroupMember.user_id == uid).all()
 
             for perm in user_perms_from_users_groups:
-                user.permissions['global'].add(perm.permission.permission_name)
+                user.permissions[GLOBAL].add(perm.permission.permission_name)
 
-            #users group repositories
-            user_repo_perms_from_users_groups = self.sa.query(
-                                                UsersGroupRepoToPerm,
-                                                Permission, Repository,)\
-                .join((Repository, UsersGroupRepoToPerm.repository_id ==
-                       Repository.repo_id))\
-                .join((Permission, UsersGroupRepoToPerm.permission_id ==
-                       Permission.permission_id))\
-                .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
-                       UsersGroupMember.users_group_id))\
-                .filter(UsersGroupMember.user_id == uid).all()
+            # users group repositories
+            user_repo_perms_from_users_groups = \
+             self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
+             .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
+             .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
+             .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
+             .filter(UsersGroupMember.user_id == uid)\
+             .all()
 
             for perm in user_repo_perms_from_users_groups:
+                r_k = perm.UsersGroupRepoToPerm.repository.repo_name
                 p = perm.Permission.permission_name
-                cur_perm = user.permissions['repositories'][perm.
-                                                    UsersGroupRepoToPerm.
-                                                    repository.repo_name]
-                #overwrite permission only if it's greater than permission
+                cur_perm = user.permissions[RK][r_k]
+                # overwrite permission only if it's greater than permission
                 # given from other sources
                 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
-                    user.permissions['repositories'][perm.UsersGroupRepoToPerm.
-                                                     repository.repo_name] = p
+                    user.permissions[RK][r_k] = p
+
+            #==================================================================
+            # get access for this user for repos group and override defaults
+            #==================================================================
+
+            # user repositories groups
+            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)\
+             .all()
+
+            for perm in user_repo_groups_perms:
+                rg_k = perm.UserRepoGroupToPerm.group.group_name
+                p = perm.Permission.permission_name
+                cur_perm = user.permissions[GK][rg_k]
+                if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
+                    user.permissions[GK][rg_k] = p
 
         return user
 
+    def has_perm(self, user, perm):
+        if not isinstance(perm, Permission):
+            raise Exception('perm needs to be an instance of Permission class '
+                            'got %s instead' % type(perm))
+
+        user = self.__get_user(user)
+
+        return UserToPerm.query().filter(UserToPerm.user == user)\
+            .filter(UserToPerm.permission == perm).scalar() is not None
+
+    def grant_perm(self, user, perm):
+        """
+        Grant user global permissions
+
+        :param user:
+        :param perm:
+        """
+        user = self.__get_user(user)
+        perm = self.__get_perm(perm)
+        new = UserToPerm()
+        new.user = user
+        new.permission = perm
+        self.sa.add(new)
+
+    def revoke_perm(self, user, perm):
+        """
+        Revoke users global permissions
+
+        :param user:
+        :param perm:
+        """
+        user = self.__get_user(user)
+        perm = self.__get_perm(perm)
+
+        obj = UserToPerm.query().filter(UserToPerm.user == user)\
+                .filter(UserToPerm.permission == perm).scalar()
+        if obj:
+            self.sa.delete(obj)
--- a/rhodecode/model/users_group.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/model/users_group.py	Sun Feb 26 17:25:09 2012 +0200
@@ -8,6 +8,7 @@
     :created_on: Oct 1, 2011
     :author: nvinot
     :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
     :license: GPLv3, see COPYING for more details.
 """
 # This program is free software: you can redistribute it and/or modify
@@ -27,50 +28,99 @@
 import traceback
 
 from rhodecode.model import BaseModel
-from rhodecode.model.caching_query import FromCache
-from rhodecode.model.db import UsersGroupMember, UsersGroup
+from rhodecode.model.db import UsersGroupMember, UsersGroup,\
+    UsersGroupRepoToPerm, Permission, UsersGroupToPerm, User
+from rhodecode.lib.exceptions import UsersGroupsAssignedException
 
 log = logging.getLogger(__name__)
 
+
 class UsersGroupModel(BaseModel):
 
-    def get(self, users_group_id, cache = False):
-        users_group = UsersGroup.query()
-        if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                          "get_users_group_%s" % users_group_id))
-        return users_group.get(users_group_id)
+    def __get_user(self, user):
+        return self._get_instance(User, user, callback=User.get_by_username)
+
+    def __get_users_group(self, users_group):
+        return self._get_instance(UsersGroup, users_group,
+                                  callback=UsersGroup.get_by_group_name)
+
+    def __get_perm(self, permission):
+        return self._get_instance(Permission, permission,
+                                  callback=Permission.get_by_key)
 
-    def get_by_name(self, name, cache = False, case_insensitive = False):
-        users_group = UsersGroup.query()
-        if case_insensitive:
-            users_group = users_group.filter(UsersGroup.users_group_name.ilike(name))
-        else:
-            users_group = users_group.filter(UsersGroup.users_group_name == name)
-        if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                          "get_users_group_%s" % name))
-        return users_group.scalar()
+    def get(self, users_group_id, cache=False):
+        return UsersGroup.get(users_group_id)
+
+    def get_by_name(self, name, cache=False, case_insensitive=False):
+        return UsersGroup.get_by_group_name(name, cache, case_insensitive)
 
-    def create(self, form_data):
+    def create(self, name, active=True):
         try:
-            new_users_group = UsersGroup()
-            for k, v in form_data.items():
-                setattr(new_users_group, k, v)
-
-            self.sa.add(new_users_group)
-            self.sa.commit()
-            return new_users_group
+            new = UsersGroup()
+            new.users_group_name = name
+            new.users_group_active = active
+            self.sa.add(new)
+            return new
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
+            raise
+
+    def update(self, users_group, form_data):
+
+        try:
+            users_group = self.__get_users_group(users_group)
+
+            for k, v in form_data.items():
+                if k == 'users_group_members':
+                    users_group.members = []
+                    self.sa.flush()
+                    members_list = []
+                    if v:
+                        v = [v] if isinstance(v, basestring) else v
+                        for u_id in set(v):
+                            member = UsersGroupMember(users_group.users_group_id, u_id)
+                            members_list.append(member)
+                    setattr(users_group, 'members', members_list)
+                setattr(users_group, k, v)
+
+            self.sa.add(users_group)
+        except:
+            log.error(traceback.format_exc())
+            raise
+
+    def delete(self, users_group, force=False):
+        """
+        Deletes repos group, unless force flag is used
+        raises exception if there are members in that group, else deletes
+        group and users
+
+        :param users_group:
+        :param force:
+        """
+        try:
+            users_group = self.__get_users_group(users_group)
+
+            # check if this group is not assigned to repo
+            assigned_groups = UsersGroupRepoToPerm.query()\
+                .filter(UsersGroupRepoToPerm.users_group == users_group).all()
+
+            if assigned_groups and force is False:
+                raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
+                                                   assigned_groups)
+
+            self.sa.delete(users_group)
+        except:
+            log.error(traceback.format_exc())
             raise
 
     def add_user_to_group(self, users_group, user):
+        users_group = self.__get_users_group(users_group)
+        user = self.__get_user(user)
+
         for m in users_group.members:
             u = m.user
             if u.user_id == user.user_id:
-                return m
+                return True
 
         try:
             users_group_member = UsersGroupMember()
@@ -81,9 +131,58 @@
             user.group_member.append(users_group_member)
 
             self.sa.add(users_group_member)
-            self.sa.commit()
             return users_group_member
         except:
             log.error(traceback.format_exc())
-            self.sa.rollback()
             raise
+
+    def remove_user_from_group(self, users_group, user):
+        users_group = self.__get_users_group(users_group)
+        user = self.__get_user(user)
+
+        users_group_member = None
+        for m in users_group.members:
+            if m.user.user_id == user.user_id:
+                # Found this user's membership row
+                users_group_member = m
+                break
+
+        if users_group_member:
+            try:
+                self.sa.delete(users_group_member)
+                return True
+            except:
+                log.error(traceback.format_exc())
+                raise
+        else:
+            # User isn't in that group
+            return False
+
+    def has_perm(self, users_group, perm):
+        users_group = self.__get_users_group(users_group)
+        perm = self.__get_perm(perm)
+
+        return UsersGroupToPerm.query()\
+            .filter(UsersGroupToPerm.users_group == users_group)\
+            .filter(UsersGroupToPerm.permission == perm).scalar() is not None
+
+    def grant_perm(self, users_group, perm):
+        if not isinstance(perm, Permission):
+            raise Exception('perm needs to be an instance of Permission class')
+
+        users_group = self.__get_users_group(users_group)
+
+        new = UsersGroupToPerm()
+        new.users_group = users_group
+        new.permission = perm
+        self.sa.add(new)
+
+    def revoke_perm(self, users_group, perm):
+        users_group = self.__get_users_group(users_group)
+        perm = self.__get_perm(perm)
+
+        obj = UsersGroupToPerm.query()\
+            .filter(UsersGroupToPerm.users_group == users_group)\
+            .filter(UsersGroupToPerm.permission == perm).scalar()
+        if obj:
+            self.sa.delete(obj)
--- a/rhodecode/public/css/diff.css	Sun Feb 19 20:21:14 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-div.diffblock {
-    overflow: auto;
-    padding: 0px;
-    border: 1px solid #ccc;
-    background: #f8f8f8;
-    font-size: 100%;
-    line-height: 100%;
-    /* new */
-    line-height: 125%;
-}
-div.diffblock .code-header{
-	border-bottom: 1px solid #CCCCCC;
-	background: #EEEEEE;
-	padding:10px 0 10px 0;
-}
-div.diffblock .code-header div{
-	margin-left:25px;
-	font-weight: bold;
-}
-div.diffblock .code-body{
-	background: #FFFFFF;
-}
-div.diffblock pre.raw{
-	background: #FFFFFF;
-	color:#000000;
-}
-
-table.code-difftable{
-	border-collapse: collapse;
-	width: 99%;
-}
-table.code-difftable td:target *{
-	background:  repeat scroll 0 0 #FFFFBE !important;
-	text-decoration: underline;
-}
-
-table.code-difftable td {
-    padding: 0 !important; 
-    background: none !important; 
-    border:0 !important;    
-}
-
-
-.code-difftable .context{
-	background:none repeat scroll 0 0 #DDE7EF;
-}
-.code-difftable .add{
-	background:none repeat scroll 0 0 #DDFFDD;
-}
-.code-difftable .add ins{
-	background:none repeat scroll 0 0 #AAFFAA;
-	text-decoration:none;
-}
-
-.code-difftable .del{
-	background:none repeat scroll 0 0 #FFDDDD;
-}
-.code-difftable .del del{
-	background:none repeat scroll 0 0 #FFAAAA;
-	text-decoration:none;
-}
-
-.code-difftable .lineno{
-	background:none repeat scroll 0 0 #EEEEEE !important;
-	padding-left:2px;
-	padding-right:2px;
-	text-align:right;
-	width:30px;
-	-moz-user-select:none;
-	-webkit-user-select: none;
-}
-.code-difftable .new {
-	border-right: 1px solid #CCC !important;
-}
-.code-difftable .old {
-    border-right: 1px solid #CCC !important;
-}
-.code-difftable .lineno pre{
-	color:#747474 !important;
-	font:11px "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace !important;
-	letter-spacing:-1px;
-	text-align:right;
-	width:20px;
-}
-.code-difftable .lineno a{
-font-weight: 700;
-cursor: pointer;
-}
-.code-difftable .code td{
-	margin:0;
-	padding: 0;
-}
-.code-difftable .code pre{
-	margin:0;
-	padding:0;
-}
-
-.code { 
-	display: block;
-	width: 100%;
-}
-.code-diff {
-    padding: 0px;
-    margin-top: 5px;
-    margin-bottom: 5px;
-    border-left: 2px solid #ccc;
-}
-.code-diff pre, .line pre { 
-	padding: 3px;
-    margin: 0;
-}
-.lineno a { 
-	text-decoration: none; 
-}
-
-.line{
-	padding:0;
-	margin:0;
-}
\ No newline at end of file
--- a/rhodecode/public/css/pygments.css	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/public/css/pygments.css	Sun Feb 26 17:25:09 2012 +0200
@@ -7,15 +7,53 @@
     line-height: 100%;
     /* new */
     line-height: 125%;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;     
 }
 div.codeblock .code-header{
 	border-bottom: 1px solid #CCCCCC;
 	background: #EEEEEE;
 	padding:10px 0 10px 0;
 }
-div.codeblock .code-header .revision{
+
+div.codeblock .code-header .stats{
+	clear: both;
+	padding: 6px 8px 6px 10px; 
+	border-bottom: 1px solid rgb(204, 204, 204);  
+	height: 23px;
+	margin-bottom: 6px;
+}
+
+div.codeblock .code-header .stats .left{
+	float:left;
+}
+div.codeblock .code-header .stats .left.img{
+	margin-top:-2px;
+}
+div.codeblock .code-header .stats .left.item{
+	float:left;
+	padding: 0 9px 0 9px;
+	border-right:1px solid #ccc;
+}
+div.codeblock .code-header .stats .left.item pre{
+	
+}
+div.codeblock .code-header .stats .left.item.last{
+	border-right:none;
+}
+div.codeblock .code-header .stats .buttons{
+	float:right;
+	padding-right:4px;
+}
+
+div.codeblock .code-header .author{
 	margin-left:25px;
 	font-weight: bold;
+	height: 25px;
+}
+div.codeblock .code-header .author .user{
+	padding-top:3px;
 }
 div.codeblock .code-header .commit{
 	margin-left:25px;
@@ -33,10 +71,20 @@
 div.code-body {
 	background-color: #FFFFFF;
 }
-div.code-body pre .match{
+
+div.codeblock .code-header .search-path {
+	padding: 0px 0px 0px 10px;
+}
+
+div.search-code-body {
+    background-color: #FFFFFF;
+    padding: 5px 0px 5px 10px;
+}
+
+div.search-code-body pre .match{
 	background-color: #FAFFA6;
 }
-div.code-body pre .break{
+div.search-code-body pre .break{
 	background-color: #DDE7EF;
 	width: 100%;
 	color: #747474;
@@ -64,64 +112,64 @@
 .linenos a { text-decoration: none; }
 
 .code { display: block; }
-.code-highlight .hll { background-color: #ffffcc }
-.code-highlight .c { color: #408080; font-style: italic } /* Comment */
-.code-highlight .err { border: 1px solid #FF0000 } /* Error */
-.code-highlight .k { color: #008000; font-weight: bold } /* Keyword */
-.code-highlight .o { color: #666666 } /* Operator */
-.code-highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */
-.code-highlight .cp { color: #BC7A00 } /* Comment.Preproc */
-.code-highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */
-.code-highlight .cs { color: #408080; font-style: italic } /* Comment.Special */
-.code-highlight .gd { color: #A00000 } /* Generic.Deleted */
-.code-highlight .ge { font-style: italic } /* Generic.Emph */
-.code-highlight .gr { color: #FF0000 } /* Generic.Error */
-.code-highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
-.code-highlight .gi { color: #00A000 } /* Generic.Inserted */
-.code-highlight .go { color: #808080 } /* Generic.Output */
-.code-highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
-.code-highlight .gs { font-weight: bold } /* Generic.Strong */
-.code-highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
-.code-highlight .gt { color: #0040D0 } /* Generic.Traceback */
-.code-highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
-.code-highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
-.code-highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
-.code-highlight .kp { color: #008000 } /* Keyword.Pseudo */
-.code-highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
-.code-highlight .kt { color: #B00040 } /* Keyword.Type */
-.code-highlight .m { color: #666666 } /* Literal.Number */
-.code-highlight .s { color: #BA2121 } /* Literal.String */
-.code-highlight .na { color: #7D9029 } /* Name.Attribute */
-.code-highlight .nb { color: #008000 } /* Name.Builtin */
-.code-highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */
-.code-highlight .no { color: #880000 } /* Name.Constant */
-.code-highlight .nd { color: #AA22FF } /* Name.Decorator */
-.code-highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
-.code-highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
-.code-highlight .nf { color: #0000FF } /* Name.Function */
-.code-highlight .nl { color: #A0A000 } /* Name.Label */
-.code-highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
-.code-highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
-.code-highlight .nv { color: #19177C } /* Name.Variable */
-.code-highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
-.code-highlight .w { color: #bbbbbb } /* Text.Whitespace */
-.code-highlight .mf { color: #666666 } /* Literal.Number.Float */
-.code-highlight .mh { color: #666666 } /* Literal.Number.Hex */
-.code-highlight .mi { color: #666666 } /* Literal.Number.Integer */
-.code-highlight .mo { color: #666666 } /* Literal.Number.Oct */
-.code-highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
-.code-highlight .sc { color: #BA2121 } /* Literal.String.Char */
-.code-highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
-.code-highlight .s2 { color: #BA2121 } /* Literal.String.Double */
-.code-highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
-.code-highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
-.code-highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
-.code-highlight .sx { color: #008000 } /* Literal.String.Other */
-.code-highlight .sr { color: #BB6688 } /* Literal.String.Regex */
-.code-highlight .s1 { color: #BA2121 } /* Literal.String.Single */
-.code-highlight .ss { color: #19177C } /* Literal.String.Symbol */
-.code-highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
-.code-highlight .vc { color: #19177C } /* Name.Variable.Class */
-.code-highlight .vg { color: #19177C } /* Name.Variable.Global */
-.code-highlight .vi { color: #19177C } /* Name.Variable.Instance */
-.code-highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
+.code-highlight .hll, .codehilite .hll { background-color: #ffffcc }
+.code-highlight .c, .codehilite .c { color: #408080; font-style: italic } /* Comment */
+.code-highlight .err, .codehilite .err { border: 1px solid #FF0000 } /* Error */
+.code-highlight .k, .codehilite .k { color: #008000; font-weight: bold } /* Keyword */
+.code-highlight .o, .codehilite .o { color: #666666 } /* Operator */
+.code-highlight .cm, .codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */
+.code-highlight .cp, .codehilite .cp { color: #BC7A00 } /* Comment.Preproc */
+.code-highlight .c1, .codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */
+.code-highlight .cs, .codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */
+.code-highlight .gd, .codehilite .gd { color: #A00000 } /* Generic.Deleted */
+.code-highlight .ge, .codehilite .ge { font-style: italic } /* Generic.Emph */
+.code-highlight .gr, .codehilite .gr { color: #FF0000 } /* Generic.Error */
+.code-highlight .gh, .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.code-highlight .gi, .codehilite .gi { color: #00A000 } /* Generic.Inserted */
+.code-highlight .go, .codehilite .go { color: #808080 } /* Generic.Output */
+.code-highlight .gp, .codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.code-highlight .gs, .codehilite .gs { font-weight: bold } /* Generic.Strong */
+.code-highlight .gu, .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.code-highlight .gt, .codehilite .gt { color: #0040D0 } /* Generic.Traceback */
+.code-highlight .kc, .codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.code-highlight .kd, .codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.code-highlight .kn, .codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
+.code-highlight .kp, .codehilite .kp { color: #008000 } /* Keyword.Pseudo */
+.code-highlight .kr, .codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.code-highlight .kt, .codehilite .kt { color: #B00040 } /* Keyword.Type */
+.code-highlight .m, .codehilite .m { color: #666666 } /* Literal.Number */
+.code-highlight .s, .codehilite .s { color: #BA2121 } /* Literal.String */
+.code-highlight .na, .codehilite .na { color: #7D9029 } /* Name.Attribute */
+.code-highlight .nb, .codehilite .nb { color: #008000 } /* Name.Builtin */
+.code-highlight .nc, .codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
+.code-highlight .no, .codehilite .no { color: #880000 } /* Name.Constant */
+.code-highlight .nd, .codehilite .nd { color: #AA22FF } /* Name.Decorator */
+.code-highlight .ni, .codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */
+.code-highlight .ne, .codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
+.code-highlight .nf, .codehilite .nf { color: #0000FF } /* Name.Function */
+.code-highlight .nl, .codehilite .nl { color: #A0A000 } /* Name.Label */
+.code-highlight .nn, .codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
+.code-highlight .nt, .codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
+.code-highlight .nv, .codehilite .nv { color: #19177C } /* Name.Variable */
+.code-highlight .ow, .codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
+.code-highlight .w, .codehilite .w { color: #bbbbbb } /* Text.Whitespace */
+.code-highlight .mf, .codehilite .mf { color: #666666 } /* Literal.Number.Float */
+.code-highlight .mh, .codehilite .mh { color: #666666 } /* Literal.Number.Hex */
+.code-highlight .mi, .codehilite .mi { color: #666666 } /* Literal.Number.Integer */
+.code-highlight .mo, .codehilite .mo { color: #666666 } /* Literal.Number.Oct */
+.code-highlight .sb, .codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
+.code-highlight .sc, .codehilite .sc { color: #BA2121 } /* Literal.String.Char */
+.code-highlight .sd, .codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
+.code-highlight .s2, .codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
+.code-highlight .se, .codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
+.code-highlight .sh, .codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
+.code-highlight .si, .codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
+.code-highlight .sx, .codehilite .sx { color: #008000 } /* Literal.String.Other */
+.code-highlight .sr, .codehilite .sr { color: #BB6688 } /* Literal.String.Regex */
+.code-highlight .s1, .codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
+.code-highlight .ss, .codehilite .ss { color: #19177C } /* Literal.String.Symbol */
+.code-highlight .bp, .codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
+.code-highlight .vc, .codehilite .vc { color: #19177C } /* Name.Variable.Class */
+.code-highlight .vg, .codehilite .vg { color: #19177C } /* Name.Variable.Global */
+.code-highlight .vi, .codehilite .vi { color: #19177C } /* Name.Variable.Instance */
+.code-highlight .il, .codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
--- a/rhodecode/public/css/style.css	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/public/css/style.css	Sun Feb 26 17:25:09 2012 +0200
@@ -1,2759 +1,4272 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td {
-border:0;
-outline:0;
-font-size:100%;
-vertical-align:baseline;
-background:transparent;
-margin:0;
-padding:0;
+html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td
+	{
+	border: 0;
+	outline: 0;
+	font-size: 100%;
+	vertical-align: baseline;
+	background: transparent;
+	margin: 0;
+	padding: 0;
 }
 
 body {
-line-height:1;
-height:100%;
-background:url("../images/background.png") repeat scroll 0 0 #B0B0B0;
-font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
-font-size:12px;
-color:#000;
-margin:0;
-padding:0;
+	line-height: 1;
+	height: 100%;
+	background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
+	font-family: Lucida Grande, Verdana, Lucida Sans Regular,
+		Lucida Sans Unicode, Arial, sans-serif; font-size : 12px;
+	color: #000;
+	margin: 0;
+	padding: 0;
+	font-size: 12px;
 }
 
 ol,ul {
-list-style:none;
+	list-style: none;
 }
 
 blockquote,q {
-quotes:none;
+	quotes: none;
 }
 
 blockquote:before,blockquote:after,q:before,q:after {
-content:none;
+	content: none;
 }
 
 :focus {
-outline:0;
+	outline: 0;
 }
 
 del {
-text-decoration:line-through;
+	text-decoration: line-through;
 }
 
 table {
-border-collapse:collapse;
-border-spacing:0;
+	border-collapse: collapse;
+	border-spacing: 0;
 }
 
 html {
-height:100%;
+	height: 100%;
 }
 
 a {
-color:#003367;
-text-decoration:none;
-cursor:pointer;
+	color: #003367;
+	text-decoration: none;
+	cursor: pointer;
 }
 
 a:hover {
-color:#316293;
-text-decoration:underline;
+	color: #316293;
+	text-decoration: underline;
 }
 
 h1,h2,h3,h4,h5,h6 {
-color:#292929;
-font-weight:700;
+	color: #292929;
+	font-weight: 700;
 }
 
 h1 {
-font-size:22px;
+	font-size: 22px;
 }
 
 h2 {
-font-size:20px;
+	font-size: 20px;
 }
 
 h3 {
-font-size:18px;
+	font-size: 18px;
 }
 
 h4 {
-font-size:16px;
+	font-size: 16px;
 }
 
 h5 {
-font-size:14px;
+	font-size: 14px;
 }
 
 h6 {
-font-size:11px;
+	font-size: 11px;
 }
 
 ul.circle {
-list-style-type:circle;
+	list-style-type: circle;
 }
 
 ul.disc {
-list-style-type:disc;
+	list-style-type: disc;
 }
 
 ul.square {
-list-style-type:square;
+	list-style-type: square;
 }
 
 ol.lower-roman {
-list-style-type:lower-roman;
+	list-style-type: lower-roman;
 }
 
 ol.upper-roman {
-list-style-type:upper-roman;
+	list-style-type: upper-roman;
 }
 
 ol.lower-alpha {
-list-style-type:lower-alpha;
+	list-style-type: lower-alpha;
 }
 
 ol.upper-alpha {
-list-style-type:upper-alpha;
+	list-style-type: upper-alpha;
 }
 
 ol.decimal {
-list-style-type:decimal;
+	list-style-type: decimal;
 }
 
 div.color {
-clear:both;
-overflow:hidden;
-position:absolute;
-background:#FFF;
-margin:7px 0 0 60px;
-padding:1px 1px 1px 0;
+	clear: both;
+	overflow: hidden;
+	position: absolute;
+	background: #FFF;
+	margin: 7px 0 0 60px;
+	padding: 1px 1px 1px 0;
 }
 
 div.color a {
-width:15px;
-height:15px;
-display:block;
-float:left;
-margin:0 0 0 1px;
-padding:0;
+	width: 15px;
+	height: 15px;
+	display: block;
+	float: left;
+	margin: 0 0 0 1px;
+	padding: 0;
 }
 
 div.options {
-clear:both;
-overflow:hidden;
-position:absolute;
-background:#FFF;
-margin:7px 0 0 162px;
-padding:0;
+	clear: both;
+	overflow: hidden;
+	position: absolute;
+	background: #FFF;
+	margin: 7px 0 0 162px;
+	padding: 0;
 }
 
 div.options a {
-height:1%;
-display:block;
-text-decoration:none;
-margin:0;
-padding:3px 8px;
+	height: 1%;
+	display: block;
+	text-decoration: none;
+	margin: 0;
+	padding: 3px 8px;
 }
 
 .top-left-rounded-corner {
--webkit-border-top-left-radius: 8px;
--khtml-border-radius-topleft: 8px; 
--moz-border-radius-topleft: 8px;
-border-top-left-radius: 8px;
+	-webkit-border-top-left-radius: 8px;
+	-khtml-border-radius-topleft: 8px;
+	-moz-border-radius-topleft: 8px;
+	border-top-left-radius: 8px;
 }
 
 .top-right-rounded-corner {
--webkit-border-top-right-radius: 8px;
--khtml-border-radius-topright: 8px;    
--moz-border-radius-topright: 8px;
-border-top-right-radius: 8px;
+	-webkit-border-top-right-radius: 8px;
+	-khtml-border-radius-topright: 8px;
+	-moz-border-radius-topright: 8px;
+	border-top-right-radius: 8px;
 }
 
 .bottom-left-rounded-corner {
--webkit-border-bottom-left-radius: 8px;
--khtml-border-radius-bottomleft: 8px;  
--moz-border-radius-bottomleft: 8px;
-border-bottom-left-radius: 8px;
+	-webkit-border-bottom-left-radius: 8px;
+	-khtml-border-radius-bottomleft: 8px;
+	-moz-border-radius-bottomleft: 8px;
+	border-bottom-left-radius: 8px;
 }
 
 .bottom-right-rounded-corner {
--webkit-border-bottom-right-radius: 8px;
--khtml-border-radius-bottomright: 8px; 
--moz-border-radius-bottomright: 8px;
-border-bottom-right-radius: 8px;
-}
-
+	-webkit-border-bottom-right-radius: 8px;
+	-khtml-border-radius-bottomright: 8px;
+	-moz-border-radius-bottomright: 8px;
+	border-bottom-right-radius: 8px;
+}
 
 #header {
-margin:0;
-padding:0 10px;
-}
-
-
-#header ul#logged-user{
-margin-bottom:5px !important;
--webkit-border-radius: 0px 0px 8px 8px;
--khtml-border-radius: 0px 0px 8px 8px; 
--moz-border-radius: 0px 0px 8px 8px;
-border-radius: 0px 0px 8px 8px;
-height:37px;
-background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
+	margin: 0;
+	padding: 0 10px;
+}
+
+#header ul#logged-user {
+	margin-bottom: 5px !important;
+	-webkit-border-radius: 0px 0px 8px 8px;
+	-khtml-border-radius: 0px 0px 8px 8px;
+	-moz-border-radius: 0px 0px 8px 8px;
+	border-radius: 0px 0px 8px 8px;
+	height: 37px;
+    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-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 );
+	box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 }
 
 #header ul#logged-user li {
-list-style:none;
-float:left;
-margin:8px 0 0;
-padding:4px 12px;
-border-left: 1px solid #316293;
+	list-style: none;
+	float: left;
+	margin: 8px 0 0;
+	padding: 4px 12px;
+	border-left: 1px solid #316293;
 }
 
 #header ul#logged-user li.first {
-border-left:none;
-margin:4px;
+	border-left: none;
+	margin: 4px;
 }
 
 #header ul#logged-user li.first div.gravatar {
-margin-top:-2px;
+	margin-top: -2px;
 }
 
 #header ul#logged-user li.first div.account {
-padding-top:4px;
-float:left;
+	padding-top: 4px;
+	float: left;
 }
 
 #header ul#logged-user li.last {
-border-right:none;
+	border-right: none;
 }
 
 #header ul#logged-user li a {
-color:#fff;
-font-weight:700;
-text-decoration:none;
+	color: #fff;
+	font-weight: 700;
+	text-decoration: none;
 }
 
 #header ul#logged-user li a:hover {
-text-decoration:underline;
+	text-decoration: underline;
 }
 
 #header ul#logged-user li.highlight a {
-color:#fff;
+	color: #fff;
 }
 
 #header ul#logged-user li.highlight a:hover {
-color:#FFF;
+	color: #FFF;
 }
 
 #header #header-inner {
-min-height:40px;
-clear:both;
-position:relative;
-background:#003367 url("../images/header_inner.png") repeat-x;
-margin:0;
-padding:0;
-display:block;
-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;
-}
-
+	min-height: 44px;
+	clear: both;
+	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-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 );
+	margin: 0;
+	padding: 0;
+	display: block;
+	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;
+}
+#header #header-inner.hover{
+	position: fixed !important;
+	width: 100% !important;
+	margin-left: -10px !important;
+	z-index: 10000;
+    -webkit-border-radius: 0px 0px 0px 0px;
+    -khtml-border-radius: 0px 0px 0px 0px;
+    -moz-border-radius: 0px 0px 0px 0px;
+    border-radius: 0px 0px 0px 0px;	
+}
 #header #header-inner #home a {
-height:40px;
-width:46px;
-display:block;
-background:url("../images/button_home.png");
-background-position:0 0;
-margin:0;
-padding:0;
+	height: 40px;
+	width: 46px;
+	display: block;
+	background: url("../images/button_home.png");
+	background-position: 0 0;
+	margin: 0;
+	padding: 0;
 }
 
 #header #header-inner #home a:hover {
-background-position:0 -40px;
-}
+	background-position: 0 -40px;
+}
+
 #header #header-inner #logo {
-    float: left;
-    position: absolute;	
-}
+	float: left;
+	position: absolute;
+}
+
 #header #header-inner #logo h1 {
-color:#FFF;
-font-size:18px;
-margin:10px 0 0 13px;
-padding:0;
+	color: #FFF;
+	font-size: 20px;
+	margin: 12px 0 0 13px;
+	padding: 0;
 }
 
 #header #header-inner #logo a {
-color:#fff;
-text-decoration:none;
+	color: #fff;
+	text-decoration: none;
 }
 
 #header #header-inner #logo a:hover {
-color:#bfe3ff;
+	color: #bfe3ff;
 }
 
 #header #header-inner #quick,#header #header-inner #quick ul {
-position:relative;
-float:right;
-list-style-type:none;
-list-style-position:outside;
-margin:6px 5px 0 0;
-padding:0;
+	position: relative;
+	float: right;
+	list-style-type: none;
+	list-style-position: outside;
+	margin: 8px 8px 0 0;
+	padding: 0;
 }
 
 #header #header-inner #quick li {
-position:relative;
-float:left;
-margin:0 5px 0 0;
-padding:0;
-}
-
-#header #header-inner #quick li a {
-top:0;
-left:0;
-height:1%;
-display:block;
-clear:both;
-overflow:hidden;
-color:#FFF;
-font-weight:700;
-text-decoration:none;
-background:#369;
-padding:0;
--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;
+	position: relative;
+	float: left;
+	margin: 0 5px 0 0;
+	padding: 0;
+}
+
+#header #header-inner #quick li a.menu_link {
+	top: 0;
+	left: 0;
+	height: 1%;
+	display: block;
+	clear: both;
+	overflow: hidden;
+	color: #FFF;
+	font-weight: 700;
+	text-decoration: none;
+	background: #369;
+	padding: 0;
+	-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;
 }
 
 #header #header-inner #quick li span.short {
-padding:9px 6px 8px 6px;
+	padding: 9px 6px 8px 6px;
 }
 
 #header #header-inner #quick li span {
-top:0;
-right:0;
-height:1%;
-display:block;
-float:left;
-border-left:1px solid #3f6f9f;
-margin:0;
-padding:10px 12px 8px 10px;
+	top: 0;
+	right: 0;
+	height: 1%;
+	display: block;
+	float: left;
+	border-left: 1px solid #3f6f9f;
+	margin: 0;
+	padding: 10px 12px 8px 10px;
 }
 
 #header #header-inner #quick li span.normal {
-border:none;
-padding:10px 12px 8px;
+	border: none;
+	padding: 10px 12px 8px;
 }
 
 #header #header-inner #quick li span.icon {
-top:0;
-left:0;
-border-left:none;
-border-right:1px solid #2e5c89;
-padding:8px 6px 4px;
+	top: 0;
+	left: 0;
+	border-left: none;
+	border-right: 1px solid #2e5c89;
+	padding: 8px 6px 4px;
 }
 
 #header #header-inner #quick li span.icon_short {
-top:0;
-left:0;
-border-left:none;
-border-right:1px solid #2e5c89;
-padding:8px 6px 4px;
-}
-#header #header-inner #quick li span.icon img, #header #header-inner #quick li span.icon_short img {
+	top: 0;
+	left: 0;
+	border-left: none;
+	border-right: 1px solid #2e5c89;
+	padding: 8px 6px 4px;
+}
+
+#header #header-inner #quick li span.icon img,#header #header-inner #quick li span.icon_short img
+	{
 	margin: 0px -2px 0px 0px;
 }
 
 #header #header-inner #quick li a:hover {
-background:#4e4e4e  no-repeat top left;
+	background: #4e4e4e no-repeat top left;
 }
 
 #header #header-inner #quick li a:hover span {
-border-left:1px solid #545454;
-}
-
-#header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short {
-border-left:none;
-border-right:1px solid #464646;
+	border-left: 1px solid #545454;
+}
+
+#header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short
+	{
+	border-left: none;
+	border-right: 1px solid #464646;
 }
 
 #header #header-inner #quick ul {
-top:29px;
-right:0;
-min-width:200px;
-display:none;
-position:absolute;
-background:#FFF;
-border:1px solid #666;
-border-top:1px solid #003367;
-z-index:100;
-margin:0;
-padding:0;
+	top: 29px;
+	right: 0;
+	min-width: 200px;
+	display: none;
+	position: absolute;
+	background: #FFF;
+	border: 1px solid #666;
+	border-top: 1px solid #003367;
+	z-index: 100;
+	margin: 0px 0px 0px 0px;
+	padding: 0;
 }
 
 #header #header-inner #quick ul.repo_switcher {
-max-height:275px;
-overflow-x:hidden;
-overflow-y:auto;
-}
+	max-height: 275px;
+	overflow-x: hidden;
+	overflow-y: auto;
+}
+
 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
-float:none;
-margin:0;
-border-bottom:2px solid #003367;
-}
-
-
-#header #header-inner #quick .repo_switcher_type{
-position:absolute;
-left:0;
-top:9px; 
-
-}
+	float: none;
+	margin: 0;
+	border-bottom: 2px solid #003367;
+}
+
+#header #header-inner #quick .repo_switcher_type {
+	position: absolute;
+	left: 0;
+	top: 9px;
+}
+
 #header #header-inner #quick li ul li {
-border-bottom:1px solid #ddd;
+	border-bottom: 1px solid #ddd;
 }
 
 #header #header-inner #quick li ul li a {
-width:182px;
-height:auto;
-display:block;
-float:left;
-background:#FFF;
-color:#003367;
-font-weight:400;
-margin:0;
-padding:7px 9px;
+	width: 182px;
+	height: auto;
+	display: block;
+	float: left;
+	background: #FFF;
+	color: #003367;
+	font-weight: 400;
+	margin: 0;
+	padding: 7px 9px;
 }
 
 #header #header-inner #quick li ul li a:hover {
-color:#000;
-background:#FFF;
+	color: #000;
+	background: #FFF;
 }
 
 #header #header-inner #quick ul ul {
-top:auto;
+	top: auto;
 }
 
 #header #header-inner #quick li ul ul {
-right:200px;
-max-height:275px;
-overflow:auto;
-overflow-x:hidden;
-white-space:normal;
-}
-
-#header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover {
-background:url("../images/icons/book.png") no-repeat scroll 4px 9px #FFF;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover {
-background:url("../images/icons/lock.png") no-repeat scroll 4px 9px #FFF;
-min-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover {
-background:url("../images/icons/lock_open.png") no-repeat scroll 4px 9px #FFF;
-min-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover {
-background:url("../images/icons/hgicon.png") no-repeat scroll 4px 9px #FFF;
-min-width:167px;
-margin:0 0 0 14px;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover {
-background:url("../images/icons/giticon.png") no-repeat scroll 4px 9px #FFF;
-min-width:167px;
-margin:0 0 0 14px;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover {
-background:url("../images/icons/database_edit.png") no-repeat scroll 4px 9px #FFF;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover {
-background:url("../images/icons/database_link.png") no-repeat scroll 4px 9px #FFF;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover {
-background:#FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover {
-background:#FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover {
-background:#FFF url("../images/icons/cog.png") no-repeat 4px 9px;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover {
-background:#FFF url("../images/icons/key.png") no-repeat 4px 9px;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover {
-background:#FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover {
-background:#FFF url("../images/icons/arrow_divide.png") no-repeat 4px 9px;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover {
-background:#FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover {
-background:#FFF url("../images/icons/delete.png") no-repeat 4px 9px;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover {
-background:#FFF url("../images/icons/arrow_branch.png") no-repeat 4px 9px;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover {
-background:#FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
-#header #header-inner #quick li ul li a.admin,#header #header-inner #quick li ul li a.admin:hover {
-background:#FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
-width:167px;
-margin:0;
-padding:12px 9px 7px 24px;
-}
-
+	right: 200px;
+	max-height: 275px;
+	overflow: auto;
+	overflow-x: hidden;
+	white-space: normal;
+}
+
+#header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover
+	{
+	background: url("../images/icons/book.png") no-repeat scroll 4px 9px
+		#FFF;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover
+	{
+	background: url("../images/icons/lock.png") no-repeat scroll 4px 9px
+		#FFF;
+	min-width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover
+	{
+	background: url("../images/icons/lock_open.png") no-repeat scroll 4px
+		9px #FFF;
+	min-width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover
+	{
+	background: url("../images/icons/hgicon.png") no-repeat scroll 4px 9px
+		#FFF;
+	min-width: 167px;
+	margin: 0 0 0 14px;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover
+	{
+	background: url("../images/icons/giticon.png") no-repeat scroll 4px 9px
+		#FFF;
+	min-width: 167px;
+	margin: 0 0 0 14px;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover
+	{
+	background: url("../images/icons/database_edit.png") no-repeat scroll
+		4px 9px #FFF;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover
+	{
+	background: url("../images/icons/database_link.png") no-repeat scroll
+		4px 9px #FFF;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover
+	{
+	background: #FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover
+	{
+	background: #FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover
+	{
+	background: #FFF url("../images/icons/cog.png") no-repeat 4px 9px;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover
+	{
+	background: #FFF url("../images/icons/key.png") no-repeat 4px 9px;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover
+	{
+	background: #FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover
+	{
+	background: #FFF url("../images/icons/arrow_divide.png") no-repeat 4px
+		9px;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover
+	{
+	background: #FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover
+	{
+	background: #FFF url("../images/icons/delete.png") no-repeat 4px 9px;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover
+	{
+	background: #FFF url("../images/icons/arrow_branch.png") no-repeat 4px
+		9px;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.tags,
+#header #header-inner #quick li ul li a.tags:hover{
+	background: #FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.bookmarks,
+#header #header-inner #quick li ul li a.bookmarks:hover{
+    background: #FFF url("../images/icons/tag_green.png") no-repeat 4px 9px;
+    width: 167px;
+    margin: 0;
+    padding: 12px 9px 7px 24px;
+}
+
+#header #header-inner #quick li ul li a.admin,
+#header #header-inner #quick li ul li a.admin:hover{
+	background: #FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
+	width: 167px;
+	margin: 0;
+	padding: 12px 9px 7px 24px;
+}
 
 .groups_breadcrumbs a {
 	color: #fff;
 }
+
 .groups_breadcrumbs a:hover {
-    color: #bfe3ff;
-    text-decoration: none;
-}
-
-.quick_repo_menu{
+	color: #bfe3ff;
+	text-decoration: none;
+}
+
+td.quick_repo_menu {
 	background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
 	cursor: pointer;
 	width: 8px;
-}
-.quick_repo_menu.active{
-    background: #FFF url("../images/horizontal-indicator.png") 4px 50% no-repeat !important;
+	border: 1px solid transparent;
+}
+
+td.quick_repo_menu.active {
+    background: url("../images/dt-arrow-dn.png") no-repeat scroll 5px 50% #FFFFFF !important;
+    border: 1px solid #003367;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
     cursor: pointer;
 }
-.quick_repo_menu .menu_items{
-	margin-top:6px;
-	width:150px;
+
+td.quick_repo_menu .menu_items {
+	margin-top: 10px;
+	margin-left:-6px;
+	width: 150px;
 	position: absolute;
-	background-color:#FFF;
-    background: none repeat scroll 0 0 #FFFFFF;
-    border-color: #003367 #666666 #666666;
-    border-right: 1px solid #666666;
-    border-style: solid;
-    border-width: 1px;
-    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
-}
-.quick_repo_menu .menu_items li{
-    padding:0 !important;
-}
-.quick_repo_menu .menu_items a{
+	background-color: #FFF;
+	background: none repeat scroll 0 0 #FFFFFF;
+	border-color: #003367 #666666 #666666;
+	border-right: 1px solid #666666;
+	border-style: solid;
+	border-width: 1px;
+	box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
+	border-top-style: none;
+}
+
+td.quick_repo_menu .menu_items li {
+	padding: 0 !important;
+}
+
+td.quick_repo_menu .menu_items a {
 	display: block;
 	padding: 4px 12px 4px 8px;
 }
-.quick_repo_menu .menu_items a:hover{
-    background-color: #EEE;
+
+td.quick_repo_menu .menu_items a:hover {
+	background-color: #EEE;
+	text-decoration: none;
+}
+
+td.quick_repo_menu .menu_items .icon img {
+	margin-bottom: -2px;
+}
+
+td.quick_repo_menu .menu_items.hidden {
+	display: none;
+}
+
+.yui-dt-first th {
+	text-align: left;
+}
+
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-skin-sam .yui-dt-mask {
+    position: absolute;
+    z-index: 9500;
+}
+.yui-dt-tmp {
+    position: absolute;
+    left: -9000px;
+}
+.yui-dt-scrollable .yui-dt-bd { overflow: auto }
+.yui-dt-scrollable .yui-dt-hd {
+    overflow: hidden;
+    position: relative;
+}
+.yui-dt-scrollable .yui-dt-bd thead tr,
+.yui-dt-scrollable .yui-dt-bd thead th {
+    position: absolute;
+    left: -1500px;
+}
+.yui-dt-scrollable tbody { -moz-outline: 0 }
+.yui-skin-sam thead .yui-dt-sortable { cursor: pointer }
+.yui-skin-sam thead .yui-dt-draggable { cursor: move }
+.yui-dt-coltarget {
+    position: absolute;
+    z-index: 999;
+}
+.yui-dt-hd { zoom: 1 }
+th.yui-dt-resizeable .yui-dt-resizerliner { position: relative }
+.yui-dt-resizer {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    height: 100%;
+    cursor: e-resize;
+    cursor: col-resize;
+    background-color: #CCC;
+    opacity: 0;
+    filter: alpha(opacity=0);
+}
+.yui-dt-resizerproxy {
+    visibility: hidden;
+    position: absolute;
+    z-index: 9000;
+    background-color: #CCC;
+    opacity: 0;
+    filter: alpha(opacity=0);
+}
+th.yui-dt-hidden .yui-dt-liner,
+td.yui-dt-hidden .yui-dt-liner,
+th.yui-dt-hidden .yui-dt-resizer { display: none }
+.yui-dt-editor,
+.yui-dt-editor-shim {
+    position: absolute;
+    z-index: 9000;
+}
+.yui-skin-sam .yui-dt table {
+    margin: 0;
+    padding: 0;
+    font-family: arial;
+    font-size: inherit;
+    border-collapse: separate;
+    *border-collapse: collapse;
+    border-spacing: 0;
+    border: 1px solid #7f7f7f;
+}
+.yui-skin-sam .yui-dt thead { border-spacing: 0 }
+.yui-skin-sam .yui-dt caption {
+    color: #000;
+    font-size: 85%;
+    font-weight: normal;
+    font-style: italic;
+    line-height: 1;
+    padding: 1em 0;
+    text-align: center;
+}
+.yui-skin-sam .yui-dt th { background: #d8d8da url(../images/sprite.png) repeat-x 0 0 }
+.yui-skin-sam .yui-dt th,
+.yui-skin-sam .yui-dt th a {
+    font-weight: normal;
     text-decoration: none;
-    
-}
-.quick_repo_menu .menu_items .icon img{
-	margin-bottom:-2px;
-}
-.quick_repo_menu .menu_items.hidden{
-	display: none;
+    color: #000;
+    vertical-align: bottom;
+}
+.yui-skin-sam .yui-dt th {
+    margin: 0;
+    padding: 0;
+    border: 0;
+    border-right: 1px solid #cbcbcb;
+}
+.yui-skin-sam .yui-dt tr.yui-dt-first td { border-top: 1px solid #7f7f7f }
+.yui-skin-sam .yui-dt th .yui-dt-liner { white-space: nowrap }
+.yui-skin-sam .yui-dt-liner {
+    margin: 0;
+    padding: 0;
+}
+.yui-skin-sam .yui-dt-coltarget {
+    width: 5px;
+    background-color: red;
+}
+.yui-skin-sam .yui-dt td {
+    margin: 0;
+    padding: 0;
+    border: 0;
+    border-right: 1px solid #cbcbcb;
+    text-align: left;
+}
+.yui-skin-sam .yui-dt-list td { border-right: 0 }
+.yui-skin-sam .yui-dt-resizer { width: 6px }
+.yui-skin-sam .yui-dt-mask {
+    background-color: #000;
+    opacity: .25;
+    filter: alpha(opacity=25);
+}
+.yui-skin-sam .yui-dt-message { background-color: #FFF }
+.yui-skin-sam .yui-dt-scrollable table { border: 0 }
+.yui-skin-sam .yui-dt-scrollable .yui-dt-hd {
+    border-left: 1px solid #7f7f7f;
+    border-top: 1px solid #7f7f7f;
+    border-right: 1px solid #7f7f7f;
+}
+.yui-skin-sam .yui-dt-scrollable .yui-dt-bd {
+    border-left: 1px solid #7f7f7f;
+    border-bottom: 1px solid #7f7f7f;
+    border-right: 1px solid #7f7f7f;
+    background-color: #FFF;
+}
+.yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td { border-bottom: 1px solid #7f7f7f }
+.yui-skin-sam th.yui-dt-asc,
+.yui-skin-sam th.yui-dt-desc { background: url(../images/sprite.png) repeat-x 0 -100px }
+.yui-skin-sam th.yui-dt-sortable .yui-dt-label { margin-right: 10px }
+.yui-skin-sam th.yui-dt-asc .yui-dt-liner { background: url(../images/dt-arrow-up.png) no-repeat right }
+.yui-skin-sam th.yui-dt-desc .yui-dt-liner { background: url(../images/dt-arrow-dn.png) no-repeat right }
+tbody .yui-dt-editable { cursor: pointer }
+.yui-dt-editor {
+    text-align: left;
+    background-color: #f2f2f2;
+    border: 1px solid #808080;
+    padding: 6px;
+}
+.yui-dt-editor label {
+    padding-left: 4px;
+    padding-right: 6px;
+}
+.yui-dt-editor .yui-dt-button {
+    padding-top: 6px;
+    text-align: right;
+}
+.yui-dt-editor .yui-dt-button button {
+    background: url(../images/sprite.png) repeat-x 0 0;
+    border: 1px solid #999;
+    width: 4em;
+    height: 1.8em;
+    margin-left: 6px;
+}
+.yui-dt-editor .yui-dt-button button.yui-dt-default {
+    background: url(../images/sprite.png) repeat-x 0 -1400px;
+    background-color: #5584e0;
+    border: 1px solid #304369;
+    color: #FFF;
+}
+.yui-dt-editor .yui-dt-button button:hover {
+    background: url(../images/sprite.png) repeat-x 0 -1300px;
+    color: #000;
+}
+.yui-dt-editor .yui-dt-button button:active {
+    background: url(../images/sprite.png) repeat-x 0 -1700px;
+    color: #000;
+}
+.yui-skin-sam tr.yui-dt-even { background-color: #FFF }
+.yui-skin-sam tr.yui-dt-odd { background-color: #edf5ff }
+.yui-skin-sam tr.yui-dt-even td.yui-dt-asc,
+.yui-skin-sam tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
+.yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,
+.yui-skin-sam tr.yui-dt-odd td.yui-dt-desc { background-color: #dbeaff }
+.yui-skin-sam .yui-dt-list tr.yui-dt-even { background-color: #FFF }
+.yui-skin-sam .yui-dt-list tr.yui-dt-odd { background-color: #FFF }
+.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,
+.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
+.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,
+.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc { background-color: #edf5ff }
+.yui-skin-sam th.yui-dt-highlighted,
+.yui-skin-sam th.yui-dt-highlighted a { background-color: #b2d2ff }
+.yui-skin-sam tr.yui-dt-highlighted,
+.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,
+.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,
+.yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,
+.yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted {
+    cursor: pointer;
+    background-color: #b2d2ff;
+}
+.yui-skin-sam .yui-dt-list th.yui-dt-highlighted,
+.yui-skin-sam .yui-dt-list th.yui-dt-highlighted a { background-color: #b2d2ff }
+.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,
+.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,
+.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,
+.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,
+.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted {
+    cursor: pointer;
+    background-color: #b2d2ff;
+}
+.yui-skin-sam th.yui-dt-selected,
+.yui-skin-sam th.yui-dt-selected a { background-color: #446cd7 }
+.yui-skin-sam tr.yui-dt-selected td,
+.yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,
+.yui-skin-sam tr.yui-dt-selected td.yui-dt-desc {
+    background-color: #426fd9;
+    color: #FFF;
+}
+.yui-skin-sam tr.yui-dt-even td.yui-dt-selected,
+.yui-skin-sam tr.yui-dt-odd td.yui-dt-selected {
+    background-color: #446cd7;
+    color: #FFF;
+}
+.yui-skin-sam .yui-dt-list th.yui-dt-selected,
+.yui-skin-sam .yui-dt-list th.yui-dt-selected a { background-color: #446cd7 }
+.yui-skin-sam .yui-dt-list tr.yui-dt-selected td,
+.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,
+.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc {
+    background-color: #426fd9;
+    color: #FFF;
+}
+.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,
+.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected {
+    background-color: #446cd7;
+    color: #FFF;
+}
+.yui-skin-sam .yui-dt-paginator {
+    display: block;
+    margin: 6px 0;
+    white-space: nowrap;
+}
+.yui-skin-sam .yui-dt-paginator .yui-dt-first,
+.yui-skin-sam .yui-dt-paginator .yui-dt-last,
+.yui-skin-sam .yui-dt-paginator .yui-dt-selected { padding: 2px 6px }
+.yui-skin-sam .yui-dt-paginator a.yui-dt-first,
+.yui-skin-sam .yui-dt-paginator a.yui-dt-last { text-decoration: none }
+.yui-skin-sam .yui-dt-paginator .yui-dt-previous,
+.yui-skin-sam .yui-dt-paginator .yui-dt-next { display: none }
+.yui-skin-sam a.yui-dt-page {
+    border: 1px solid #cbcbcb;
+    padding: 2px 6px;
+    text-decoration: none;
+    background-color: #fff;
+}
+.yui-skin-sam .yui-dt-selected {
+    border: 1px solid #fff;
+    background-color: #fff;
 }
 
 #content #left {
-left:0;
-width:280px;
-position:absolute;
+	left: 0;
+	width: 280px;
+	position: absolute;
 }
 
 #content #right {
-margin:0 60px 10px 290px;
+	margin: 0 60px 10px 290px;
 }
 
 #content div.box {
-clear:both;
-overflow:hidden;
-background:#fff;
-margin:0 0 10px;
-padding:0 0 10px;
--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;
-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-
+	clear: both;
+	overflow: hidden;
+	background: #fff;
+	margin: 0 0 10px;
+	padding: 0 0 10px;
+	-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;
+	box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 }
 
 #content div.box-left {
-width:49%;
-clear:none;
-float:left;
-margin:0 0 10px;
+	width: 49%;
+	clear: none;
+	float: left;
+	margin: 0 0 10px;
 }
 
 #content div.box-right {
-width:49%;
-clear:none;
-float:right;
-margin:0 0 10px;
+	width: 49%;
+	clear: none;
+	float: right;
+	margin: 0 0 10px;
 }
 
 #content div.box div.title {
-clear:both;
-overflow:hidden;
-background:#369 url("../images/header_inner.png") repeat-x;
-margin:0 0 20px;
-padding:0;
+	clear: both;
+	overflow: hidden;
+	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-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 );
+	margin: 0 0 20px;
+	padding: 0;
 }
 
 #content div.box div.title h5 {
-float:left;
-border:none;
-color:#fff;
-text-transform:uppercase;
-margin:0;
-padding:11px 0 11px 10px;
+	float: left;
+	border: none;
+	color: #fff;
+	text-transform: uppercase;
+	margin: 0;
+	padding: 11px 0 11px 10px;
+}
+
+#content div.box div.title .link-white{
+	color: #FFFFFF;
 }
 
 #content div.box div.title ul.links li {
-list-style:none;
-float:left;
-margin:0;
-padding:0;
+	list-style: none;
+	float: left;
+	margin: 0;
+	padding: 0;
 }
 
 #content div.box div.title ul.links li a {
-    border-left: 1px solid #316293;
-    color: #FFFFFF;
-    display: block;
-    float: left;
-    font-size: 13px;
-    font-weight: 700;
-    height: 1%;
-    margin: 0;
-    padding: 11px 22px 12px;
-    text-decoration: none;
-}
-
-#content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6 {
-clear:both;
-overflow:hidden;
-border-bottom:1px solid #DDD;
-margin:10px 20px;
-padding:0 0 15px;
+	border-left: 1px solid #316293;
+	color: #FFFFFF;
+	display: block;
+	float: left;
+	font-size: 13px;
+	font-weight: 700;
+	height: 1%;
+	margin: 0;
+	padding: 11px 22px 12px;
+	text-decoration: none;
+}
+
+#content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6
+	{
+	clear: both;
+	overflow: hidden;
+	border-bottom: 1px solid #DDD;
+	margin: 10px 20px;
+	padding: 0 0 15px;
 }
 
 #content div.box p {
-color:#5f5f5f;
-font-size:12px;
-line-height:150%;
-margin:0 24px 10px;
-padding:0;
+	color: #5f5f5f;
+	font-size: 12px;
+	line-height: 150%;
+	margin: 0 24px 10px;
+	padding: 0;
 }
 
 #content div.box blockquote {
-border-left:4px solid #DDD;
-color:#5f5f5f;
-font-size:11px;
-line-height:150%;
-margin:0 34px;
-padding:0 0 0 14px;
+	border-left: 4px solid #DDD;
+	color: #5f5f5f;
+	font-size: 11px;
+	line-height: 150%;
+	margin: 0 34px;
+	padding: 0 0 0 14px;
 }
 
 #content div.box blockquote p {
-margin:10px 0;
-padding:0;
+	margin: 10px 0;
+	padding: 0;
 }
 
 #content div.box dl {
-margin:10px 24px;
+	margin: 10px 0px;
 }
 
 #content div.box dt {
-font-size:12px;
-margin:0;
+	font-size: 12px;
+	margin: 0;
 }
 
 #content div.box dd {
-font-size:12px;
-margin:0;
-padding:8px 0 8px 15px;
+	font-size: 12px;
+	margin: 0;
+	padding: 8px 0 8px 15px;
 }
 
 #content div.box li {
-font-size:12px;
-padding:4px 0;
+	font-size: 12px;
+	padding: 4px 0;
 }
 
 #content div.box ul.disc,#content div.box ul.circle {
-margin:10px 24px 10px 38px;
+	margin: 10px 24px 10px 38px;
 }
 
 #content div.box ul.square {
-margin:10px 24px 10px 40px;
+	margin: 10px 24px 10px 40px;
 }
 
 #content div.box img.left {
-border:none;
-float:left;
-margin:10px 10px 10px 0;
+	border: none;
+	float: left;
+	margin: 10px 10px 10px 0;
 }
 
 #content div.box img.right {
-border:none;
-float:right;
-margin:10px 0 10px 10px;
+	border: none;
+	float: right;
+	margin: 10px 0 10px 10px;
 }
 
 #content div.box div.messages {
-clear:both;
-overflow:hidden;
-margin:0 20px;
-padding:0;
+	clear: both;
+	overflow: hidden;
+	margin: 0 20px;
+	padding: 0;
 }
 
 #content div.box div.message {
-clear:both;
-overflow:hidden;
-margin:0;
-padding:10px 0;
+	clear: both;
+	overflow: hidden;
+	margin: 0;
+	padding: 5px 0;
+    white-space: pre-wrap;
+}
+#content div.box div.expand {
+	width: 110%;
+	height:14px;
+	font-size:10px;
+	text-align:center;
+	cursor: pointer;
+	color:#666;
+
+	background:-webkit-gradient(linear,0% 50%,100% 50%,color-stop(0%,rgba(255,255,255,0)),color-stop(100%,rgba(64,96,128,0.1)));
+	background:-webkit-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
+	background:-moz-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
+	background:-o-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
+	background:-ms-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
+	background:linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
+
+	display: none;
+}
+#content div.box div.expand .expandtext {
+	background-color: #ffffff;
+	padding: 2px;
+	border-radius: 2px;
 }
 
 #content div.box div.message a {
-font-weight:400 !important;
+	font-weight: 400 !important;
 }
 
 #content div.box div.message div.image {
-float:left;
-margin:9px 0 0 5px;
-padding:6px;
+	float: left;
+	margin: 9px 0 0 5px;
+	padding: 6px;
 }
 
 #content div.box div.message div.image img {
-vertical-align:middle;
-margin:0;
+	vertical-align: middle;
+	margin: 0;
 }
 
 #content div.box div.message div.text {
-float:left;
-margin:0;
-padding:9px 6px;
+	float: left;
+	margin: 0;
+	padding: 9px 6px;
 }
 
 #content div.box div.message div.dismiss a {
-height:16px;
-width:16px;
-display:block;
-background:url("../images/icons/cross.png") no-repeat;
-margin:15px 14px 0 0;
-padding:0;
-}
-
-#content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6 {
-border:none;
-margin:0;
-padding:0;
+	height: 16px;
+	width: 16px;
+	display: block;
+	background: url("../images/icons/cross.png") no-repeat;
+	margin: 15px 14px 0 0;
+	padding: 0;
+}
+
+#content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6
+	{
+	border: none;
+	margin: 0;
+	padding: 0;
 }
 
 #content div.box div.message div.text span {
-height:1%;
-display:block;
-margin:0;
-padding:5px 0 0;
+	height: 1%;
+	display: block;
+	margin: 0;
+	padding: 5px 0 0;
 }
 
 #content div.box div.message-error {
-height:1%;
-clear:both;
-overflow:hidden;
-background:#FBE3E4;
-border:1px solid #FBC2C4;
-color:#860006;
+	height: 1%;
+	clear: both;
+	overflow: hidden;
+	background: #FBE3E4;
+	border: 1px solid #FBC2C4;
+	color: #860006;
 }
 
 #content div.box div.message-error h6 {
-color:#860006;
+	color: #860006;
 }
 
 #content div.box div.message-warning {
-height:1%;
-clear:both;
-overflow:hidden;
-background:#FFF6BF;
-border:1px solid #FFD324;
-color:#5f5200;
+	height: 1%;
+	clear: both;
+	overflow: hidden;
+	background: #FFF6BF;
+	border: 1px solid #FFD324;
+	color: #5f5200;
 }
 
 #content div.box div.message-warning h6 {
-color:#5f5200;
+	color: #5f5200;
 }
 
 #content div.box div.message-notice {
-height:1%;
-clear:both;
-overflow:hidden;
-background:#8FBDE0;
-border:1px solid #6BACDE;
-color:#003863;
+	height: 1%;
+	clear: both;
+	overflow: hidden;
+	background: #8FBDE0;
+	border: 1px solid #6BACDE;
+	color: #003863;
 }
 
 #content div.box div.message-notice h6 {
-color:#003863;
+	color: #003863;
 }
 
 #content div.box div.message-success {
-height:1%;
-clear:both;
-overflow:hidden;
-background:#E6EFC2;
-border:1px solid #C6D880;
-color:#4e6100;
+	height: 1%;
+	clear: both;
+	overflow: hidden;
+	background: #E6EFC2;
+	border: 1px solid #C6D880;
+	color: #4e6100;
 }
 
 #content div.box div.message-success h6 {
-color:#4e6100;
+	color: #4e6100;
 }
 
 #content div.box div.form div.fields div.field {
-height:1%;
-border-bottom:1px solid #DDD;
-clear:both;
-margin:0;
-padding:10px 0;
+	height: 1%;
+	border-bottom: 1px solid #DDD;
+	clear: both;
+	margin: 0;
+	padding: 10px 0;
 }
 
 #content div.box div.form div.fields div.field-first {
-padding:0 0 10px;
+	padding: 0 0 10px;
 }
 
 #content div.box div.form div.fields div.field-noborder {
-border-bottom:0 !important;
+	border-bottom: 0 !important;
 }
 
 #content div.box div.form div.fields div.field span.error-message {
-height:1%;
-display:inline-block;
-color:red;
-margin:8px 0 0 4px;
-padding:0;
+	height: 1%;
+	display: inline-block;
+	color: red;
+	margin: 8px 0 0 4px;
+	padding: 0;
 }
 
 #content div.box div.form div.fields div.field span.success {
-height:1%;
-display:block;
-color:#316309;
-margin:8px 0 0;
-padding:0;
+	height: 1%;
+	display: block;
+	color: #316309;
+	margin: 8px 0 0;
+	padding: 0;
 }
 
 #content div.box div.form div.fields div.field div.label {
-left:70px;
-width:155px;
-position:absolute;
-margin:0;
-padding:8px 0 0 5px;
-}
-
-#content div.box-left div.form div.fields div.field div.label,#content div.box-right div.form div.fields div.field div.label {
-clear:both;
-overflow:hidden;
-left:0;
-width:auto;
-position:relative;
-margin:0;
-padding:0 0 8px;
+	left: 70px;
+	width: 155px;
+	position: absolute;
+	margin: 0;
+	padding: 5px 0 0 0px;
+}
+
+#content div.box div.form div.fields div.field div.label-summary {
+    left: 30px;
+    width: 155px;
+    position: absolute;
+    margin: 0;
+    padding: 0px 0 0 0px;
+}
+
+#content div.box-left div.form div.fields div.field div.label,
+#content div.box-right div.form div.fields div.field div.label,
+#content div.box-left div.form div.fields div.field div.label,
+#content div.box-left div.form div.fields div.field div.label-summary,
+#content div.box-right div.form div.fields div.field div.label-summary,
+#content div.box-left div.form div.fields div.field div.label-summary
+	{
+	clear: both;
+	overflow: hidden;
+	left: 0;
+	width: auto;
+	position: relative;
+	margin: 0;
+	padding: 0 0 8px;
 }
 
 #content div.box div.form div.fields div.field div.label-select {
-padding:5px 0 0 5px;
-}
-
-#content div.box-left div.form div.fields div.field div.label-select,#content div.box-right div.form div.fields div.field div.label-select {
-padding:0 0 8px;
-}
-
-#content div.box-left div.form div.fields div.field div.label-textarea,#content div.box-right div.form div.fields div.field div.label-textarea {
-padding:0 0 8px !important;
-}
-
-#content div.box div.form div.fields div.field div.label label, div.label label{
-color:#393939;
-font-weight:700;
-}
-
+	padding: 5px 0 0 5px;
+}
+
+#content div.box-left div.form div.fields div.field div.label-select,
+#content div.box-right div.form div.fields div.field div.label-select
+	{
+	padding: 0 0 8px;
+}
+
+#content div.box-left div.form div.fields div.field div.label-textarea,
+#content div.box-right div.form div.fields div.field div.label-textarea
+	{
+	padding: 0 0 8px !important;
+}
+
+#content div.box div.form div.fields div.field div.label label,div.label label
+	{
+	color: #393939;
+	font-weight: 700;
+}
+#content div.box div.form div.fields div.field div.label label,div.label-summary label
+    {
+    color: #393939;
+    font-weight: 700;
+}
 #content div.box div.form div.fields div.field div.input {
-margin:0 0 0 200px;
+	margin: 0 0 0 200px;
+}
+
+#content div.box div.form div.fields div.field div.input.summary {
+    margin: 0 0 0 110px;
+}
+#content div.box div.form div.fields div.field div.input.summary-short {
+    margin: 0 0 0 110px;
 }
 #content div.box div.form div.fields div.field div.file {
-margin:0 0 0 200px;
-}
-#content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input {
-margin:0 0 0 0px;
+	margin: 0 0 0 200px;
+}
+
+#content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input
+	{
+	margin: 0 0 0 0px;
 }
 
 #content div.box div.form div.fields div.field div.input input {
-background:#FFF;
-border-top:1px solid #b3b3b3;
-border-left:1px solid #b3b3b3;
-border-right:1px solid #eaeaea;
-border-bottom:1px solid #eaeaea;
-color:#000;
-font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
-font-size:11px;
-margin:0;
-padding:7px 7px 6px;
+	background: #FFF;
+	border-top: 1px solid #b3b3b3;
+	border-left: 1px solid #b3b3b3;
+	border-right: 1px solid #eaeaea;
+	border-bottom: 1px solid #eaeaea;
+	color: #000;
+	font-size: 11px;
+	margin: 0;
+	padding: 7px 7px 6px;
+}
+
+#content div.box div.form div.fields div.field div.input input#clone_url,
+#content div.box div.form div.fields div.field div.input input#clone_url_id
+{
+    font-size: 16px;
+    padding: 2px;	
 }
 
 #content div.box div.form div.fields div.field div.file input {
-    background: none repeat scroll 0 0 #FFFFFF;
-    border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
-    border-style: solid;
-    border-width: 1px;
-    color: #000000;
-    font-family: Lucida Grande,Verdana,Lucida Sans Regular,Lucida Sans Unicode,Arial,sans-serif;
-    font-size: 11px;
-    margin: 0;
-    padding: 7px 7px 6px;
+	background: none repeat scroll 0 0 #FFFFFF;
+	border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
+	border-style: solid;
+	border-width: 1px;
+	color: #000000;
+	font-size: 11px;
+	margin: 0;
+	padding: 7px 7px 6px;
 }
 
 input.disabled {
     background-color: #F5F5F5 !important;	
 }
-
 #content div.box div.form div.fields div.field div.input input.small {
-width:30%;
+	width: 30%;
 }
 
 #content div.box div.form div.fields div.field div.input input.medium {
-width:55%;
+	width: 55%;
 }
 
 #content div.box div.form div.fields div.field div.input input.large {
-width:85%;
+	width: 85%;
 }
 
 #content div.box div.form div.fields div.field div.input input.date {
-width:177px;
+	width: 177px;
 }
 
 #content div.box div.form div.fields div.field div.input input.button {
-background:#D4D0C8;
-border-top:1px solid #FFF;
-border-left:1px solid #FFF;
-border-right:1px solid #404040;
-border-bottom:1px solid #404040;
-color:#000;
-margin:0;
-padding:4px 8px;
+	background: #D4D0C8;
+	border-top: 1px solid #FFF;
+	border-left: 1px solid #FFF;
+	border-right: 1px solid #404040;
+	border-bottom: 1px solid #404040;
+	color: #000;
+	margin: 0;
+	padding: 4px 8px;
 }
 
 #content div.box div.form div.fields div.field div.textarea {
-border-top:1px solid #b3b3b3;
-border-left:1px solid #b3b3b3;
-border-right:1px solid #eaeaea;
-border-bottom:1px solid #eaeaea;
-margin:0 0 0 200px;
-padding:10px;
+	border-top: 1px solid #b3b3b3;
+	border-left: 1px solid #b3b3b3;
+	border-right: 1px solid #eaeaea;
+	border-bottom: 1px solid #eaeaea;
+	margin: 0 0 0 200px;
+	padding: 10px;
 }
 
 #content div.box div.form div.fields div.field div.textarea-editor {
-border:1px solid #ddd;
-padding:0;
+	border: 1px solid #ddd;
+	padding: 0;
 }
 
 #content div.box div.form div.fields div.field div.textarea textarea {
-width:100%;
-height:220px;
-overflow:hidden;
-background:#FFF;
-color:#000;
-font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
-font-size:11px;
-outline:none;
-border-width:0;
-margin:0;
-padding:0;
-}
-
-#content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea {
-width:100%;
-height:100px;
+	width: 100%;
+	height: 220px;
+	overflow: hidden;
+	background: #FFF;
+	color: #000;
+	font-size: 11px;
+	outline: none;
+	border-width: 0;
+	margin: 0;
+	padding: 0;
+}
+
+#content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea
+	{
+	width: 100%;
+	height: 100px;
 }
 
 #content div.box div.form div.fields div.field div.textarea table {
-width:100%;
-border:none;
-margin:0;
-padding:0;
+	width: 100%;
+	border: none;
+	margin: 0;
+	padding: 0;
 }
 
 #content div.box div.form div.fields div.field div.textarea table td {
-background:#DDD;
-border:none;
-padding:0;
-}
-
-#content div.box div.form div.fields div.field div.textarea table td table {
-width:auto;
-border:none;
-margin:0;
-padding:0;
-}
-
-#content div.box div.form div.fields div.field div.textarea table td table td {
-font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
-font-size:11px;
-padding:5px 5px 5px 0;
-}
-
-#content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus {
-background:#f6f6f6;
-border-color:#666;
+	background: #DDD;
+	border: none;
+	padding: 0;
+}
+
+#content div.box div.form div.fields div.field div.textarea table td table
+	{
+	width: auto;
+	border: none;
+	margin: 0;
+	padding: 0;
+}
+
+#content div.box div.form div.fields div.field div.textarea table td table td
+	{
+	font-size: 11px;
+	padding: 5px 5px 5px 0;
+}
+
+#content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus
+	{
+	background: #f6f6f6;
+	border-color: #666;
 }
 
 div.form div.fields div.field div.button {
-margin:0;
-padding:0 0 0 8px;
-}
-
+	margin: 0;
+	padding: 0 0 0 8px;
+}
+#content div.box table.noborder {
+	border: 1px solid transparent;
+}
 
 #content div.box table {
-width:100%;
-border-collapse:collapse;
-margin:0;
-padding:0;
-border: 1px solid #eee;
+	width: 100%;
+	border-collapse: separate;
+	margin: 0;
+	padding: 0;
+	border: 1px solid #eee;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;	
 }
 
 #content div.box table th {
-background:#eee;
-border-bottom:1px solid #ddd;
-padding:5px 0px 5px 5px;
+	background: #eee;
+	border-bottom: 1px solid #ddd;
+	padding: 5px 0px 5px 5px;
 }
 
 #content div.box table th.left {
-text-align:left;
+	text-align: left;
 }
 
 #content div.box table th.right {
-text-align:right;
+	text-align: right;
 }
 
 #content div.box table th.center {
-text-align:center;
+	text-align: center;
 }
 
 #content div.box table th.selected {
-vertical-align:middle;
-padding:0;
+	vertical-align: middle;
+	padding: 0;
 }
 
 #content div.box table td {
-background:#fff;
-border-bottom:1px solid #cdcdcd;
-vertical-align:middle;
-padding:5px;
+	background: #fff;
+	border-bottom: 1px solid #cdcdcd;
+	vertical-align: middle;
+	padding: 5px;
 }
 
 #content div.box table tr.selected td {
-background:#FFC;
+	background: #FFC;
 }
 
 #content div.box table td.selected {
-width:3%;
-text-align:center;
-vertical-align:middle;
-padding:0;
+	width: 3%;
+	text-align: center;
+	vertical-align: middle;
+	padding: 0;
 }
 
 #content div.box table td.action {
-width:45%;
-text-align:left;
+	width: 45%;
+	text-align: left;
 }
 
 #content div.box table td.date {
-width:33%;
-text-align:center;
+	width: 33%;
+	text-align: center;
 }
 
 #content div.box div.action {
-float:right;
-background:#FFF;
-text-align:right;
-margin:10px 0 0;
-padding:0;
+	float: right;
+	background: #FFF;
+	text-align: right;
+	margin: 10px 0 0;
+	padding: 0;
 }
 
 #content div.box div.action select {
-font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
-font-size:11px;
-margin:0;
+	font-size: 11px;
+	margin: 0;
 }
 
 #content div.box div.action .ui-selectmenu {
-margin:0;
-padding:0;
+	margin: 0;
+	padding: 0;
 }
 
 #content div.box div.pagination {
-height:1%;
-clear:both;
-overflow:hidden;
-margin:10px 0 0;
-padding:0;
+	height: 1%;
+	clear: both;
+	overflow: hidden;
+	margin: 10px 0 0;
+	padding: 0;
 }
 
 #content div.box div.pagination ul.pager {
-float:right;
-text-align:right;
-margin:0;
-padding:0;
+	float: right;
+	text-align: right;
+	margin: 0;
+	padding: 0;
 }
 
 #content div.box div.pagination ul.pager li {
-height:1%;
-float:left;
-list-style:none;
-background:#ebebeb url("../images/pager.png") repeat-x;
-border-top:1px solid #dedede;
-border-left:1px solid #cfcfcf;
-border-right:1px solid #c4c4c4;
-border-bottom:1px solid #c4c4c4;
-color:#4A4A4A;
-font-weight:700;
-margin:0 0 0 4px;
-padding:0;
+	height: 1%;
+	float: left;
+	list-style: none;
+	background: #ebebeb url("../images/pager.png") repeat-x;
+	border-top: 1px solid #dedede;
+	border-left: 1px solid #cfcfcf;
+	border-right: 1px solid #c4c4c4;
+	border-bottom: 1px solid #c4c4c4;
+	color: #4A4A4A;
+	font-weight: 700;
+	margin: 0 0 0 4px;
+	padding: 0;
 }
 
 #content div.box div.pagination ul.pager li.separator {
-padding:6px;
+	padding: 6px;
 }
 
 #content div.box div.pagination ul.pager li.current {
-background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
-border-top:1px solid #ccc;
-border-left:1px solid #bebebe;
-border-right:1px solid #b1b1b1;
-border-bottom:1px solid #afafaf;
-color:#515151;
-padding:6px;
+	background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
+	border-top: 1px solid #ccc;
+	border-left: 1px solid #bebebe;
+	border-right: 1px solid #b1b1b1;
+	border-bottom: 1px solid #afafaf;
+	color: #515151;
+	padding: 6px;
 }
 
 #content div.box div.pagination ul.pager li a {
-height:1%;
-display:block;
-float:left;
-color:#515151;
-text-decoration:none;
-margin:0;
-padding:6px;
-}
-
-#content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active {
-background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
-border-top:1px solid #ccc;
-border-left:1px solid #bebebe;
-border-right:1px solid #b1b1b1;
-border-bottom:1px solid #afafaf;
-margin:-1px;
+	height: 1%;
+	display: block;
+	float: left;
+	color: #515151;
+	text-decoration: none;
+	margin: 0;
+	padding: 6px;
+}
+
+#content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active
+	{
+	background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
+	border-top: 1px solid #ccc;
+	border-left: 1px solid #bebebe;
+	border-right: 1px solid #b1b1b1;
+	border-bottom: 1px solid #afafaf;
+	margin: -1px;
 }
 
 #content div.box div.pagination-wh {
-height:1%;
-clear:both;
-overflow:hidden;
-text-align:right;
-margin:10px 0 0;
-padding:0;
+	height: 1%;
+	clear: both;
+	overflow: hidden;
+	text-align: right;
+	margin: 10px 0 0;
+	padding: 0;
 }
 
 #content div.box div.pagination-right {
-float:right;
-}
-
-#content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot {
-height:1%;
-float:left;
-background:#ebebeb url("../images/pager.png") repeat-x;
-border-top:1px solid #dedede;
-border-left:1px solid #cfcfcf;
-border-right:1px solid #c4c4c4;
-border-bottom:1px solid #c4c4c4;
-color:#4A4A4A;
-font-weight:700;
-margin:0 0 0 4px;
-padding:6px;
+	float: right;
+}
+
+#content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot
+	{
+	height: 1%;
+	float: left;
+	background: #ebebeb url("../images/pager.png") repeat-x;
+	border-top: 1px solid #dedede;
+	border-left: 1px solid #cfcfcf;
+	border-right: 1px solid #c4c4c4;
+	border-bottom: 1px solid #c4c4c4;
+	color: #4A4A4A;
+	font-weight: 700;
+	margin: 0 0 0 4px;
+	padding: 6px;
 }
 
 #content div.box div.pagination-wh span.pager_curpage {
-height:1%;
-float:left;
-background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
-border-top:1px solid #ccc;
-border-left:1px solid #bebebe;
-border-right:1px solid #b1b1b1;
-border-bottom:1px solid #afafaf;
-color:#515151;
-font-weight:700;
-margin:0 0 0 4px;
-padding:6px;
-}
-
-#content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active {
-background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
-border-top:1px solid #ccc;
-border-left:1px solid #bebebe;
-border-right:1px solid #b1b1b1;
-border-bottom:1px solid #afafaf;
-text-decoration:none;
+	height: 1%;
+	float: left;
+	background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
+	border-top: 1px solid #ccc;
+	border-left: 1px solid #bebebe;
+	border-right: 1px solid #b1b1b1;
+	border-bottom: 1px solid #afafaf;
+	color: #515151;
+	font-weight: 700;
+	margin: 0 0 0 4px;
+	padding: 6px;
+}
+
+#content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active
+	{
+	background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
+	border-top: 1px solid #ccc;
+	border-left: 1px solid #bebebe;
+	border-right: 1px solid #b1b1b1;
+	border-bottom: 1px solid #afafaf;
+	text-decoration: none;
 }
 
 #content div.box div.traffic div.legend {
-clear:both;
-overflow:hidden;
-border-bottom:1px solid #ddd;
-margin:0 0 10px;
-padding:0 0 10px;
+	clear: both;
+	overflow: hidden;
+	border-bottom: 1px solid #ddd;
+	margin: 0 0 10px;
+	padding: 0 0 10px;
 }
 
 #content div.box div.traffic div.legend h6 {
-float:left;
-border:none;
-margin:0;
-padding:0;
+	float: left;
+	border: none;
+	margin: 0;
+	padding: 0;
 }
 
 #content div.box div.traffic div.legend li {
-list-style:none;
-float:left;
-font-size:11px;
-margin:0;
-padding:0 8px 0 4px;
+	list-style: none;
+	float: left;
+	font-size: 11px;
+	margin: 0;
+	padding: 0 8px 0 4px;
 }
 
 #content div.box div.traffic div.legend li.visits {
-border-left:12px solid #edc240;
+	border-left: 12px solid #edc240;
 }
 
 #content div.box div.traffic div.legend li.pageviews {
-border-left:12px solid #afd8f8;
+	border-left: 12px solid #afd8f8;
 }
 
 #content div.box div.traffic table {
-width:auto;
+	width: auto;
 }
 
 #content div.box div.traffic table td {
-background:transparent;
-border:none;
-padding:2px 3px 3px;
+	background: transparent;
+	border: none;
+	padding: 2px 3px 3px;
 }
 
 #content div.box div.traffic table td.legendLabel {
-padding:0 3px 2px;
-}
-
-#summary{
-
-}
-
-#summary .desc{
-white-space: pre;
-width: 100%;
-}
-
-#summary .repo_name{
-font-size: 1.6em;
-font-weight: bold;
-vertical-align: baseline;
-clear:right
-}
-
+	padding: 0 3px 2px;
+}
+
+#summary {
+	
+}
+
+#summary .desc {
+	white-space: pre;
+	width: 100%;
+}
+
+#summary .repo_name {
+	font-size: 1.6em;
+	font-weight: bold;
+	vertical-align: baseline;
+	clear: right
+}
 
 #footer {
-clear:both;
-overflow:hidden;
-text-align:right;
-margin:0;
-padding:0 10px 4px;
-margin:-10px 0 0;
+	clear: both;
+	overflow: hidden;
+	text-align: right;
+	margin: 0;
+	padding: 0 10px 4px;
+	margin: -10px 0 0;
 }
 
 #footer div#footer-inner {
-background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
-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-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-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);
+	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 {
-padding:15px 25px 15px 0;
-color:#FFF;
-font-weight:700;
-}
+	padding: 15px 25px 15px 0;
+	color: #FFF;
+	font-weight: 700;
+}
+
 #footer div#footer-inner .footer-link {
-float:left;
-padding-left:10px;
-}
-#footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a {
-color:#FFF; 
+	float: left;
+	padding-left: 10px;
+}
+
+#footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a
+	{
+	color: #FFF;
 }
 
 #login div.title {
-width:420px;
-clear:both;
-overflow:hidden;
-position:relative;
-background:#003367 url("../images/header_inner.png") repeat-x;
-margin:0 auto;
-padding:0;
+	width: 420px;
+	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-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);
+	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 {
-width:380px;
-background:#FFF url("../images/login.png") no-repeat top left;
-border-top:none;
-border-bottom:none;
-margin:0 auto;
-padding:20px;
+	width: 380px;
+	background: #FFF url("../images/login.png") no-repeat top left;
+	border-top: none;
+	border-bottom: none;
+	margin: 0 auto;
+	padding: 20px;
 }
 
 #login div.form div.fields div.field div.label {
-width:173px;
-float:left;
-text-align:right;
-margin:2px 10px 0 0;
-padding:5px 0 0 5px;
+	width: 173px;
+	float: left;
+	text-align: right;
+	margin: 2px 10px 0 0;
+	padding: 5px 0 0 5px;
 }
 
 #login div.form div.fields div.field div.input input {
-width:176px;
-background:#FFF;
-border-top:1px solid #b3b3b3;
-border-left:1px solid #b3b3b3;
-border-right:1px solid #eaeaea;
-border-bottom:1px solid #eaeaea;
-color:#000;
-font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
-font-size:11px;
-margin:0;
-padding:7px 7px 6px;
+	width: 176px;
+	background: #FFF;
+	border-top: 1px solid #b3b3b3;
+	border-left: 1px solid #b3b3b3;
+	border-right: 1px solid #eaeaea;
+	border-bottom: 1px solid #eaeaea;
+	color: #000;
+	font-size: 11px;
+	margin: 0;
+	padding: 7px 7px 6px;
 }
 
 #login div.form div.fields div.buttons {
-clear:both;
-overflow:hidden;
-border-top:1px solid #DDD;
-text-align:right;
-margin:0;
-padding:10px 0 0;
+	clear: both;
+	overflow: hidden;
+	border-top: 1px solid #DDD;
+	text-align: right;
+	margin: 0;
+	padding: 10px 0 0;
 }
 
 #login div.form div.links {
-clear:both;
-overflow:hidden;
-margin:10px 0 0;
-padding:0 0 2px;
-}
-
+	clear: both;
+	overflow: hidden;
+	margin: 10px 0 0;
+	padding: 0 0 2px;
+}
+
+.user-menu{
+    margin: 0px !important;
+    float: left;
+}
+
+.user-menu .container{
+    padding:0px 4px 0px 4px;
+    margin: 0px 0px 0px 0px;
+}
+
+.user-menu .gravatar{
+    margin: 0px 0px 0px 0px;
+    cursor: pointer;
+}
+.user-menu .gravatar.enabled{
+	background-color: #FDF784 !important;
+}
+.user-menu .gravatar:hover{
+    background-color: #FDF784 !important; 
+}
 #quick_login{
-top: 31px;
-background-color: rgb(0, 51, 103);
-z-index: 999; 
-height: 150px;
-position: absolute;
-margin-left: -16px;
-width: 281px;
--webkit-border-radius: 0px 0px 4px 4px;
--khtml-border-radius: 0px 0px 4px 4px; 
--moz-border-radius: 0px 0px 4px 4px;
-border-radius: 0px 0px 4px 4px;
-
-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-}
-
-#quick_login .password_forgoten{
-padding-right:10px;
-padding-top:0px;
-float:left;
-}
-#quick_login .password_forgoten a{
-	font-size: 10px
-}
-
-#quick_login .register{
-padding-right:10px;
-padding-top:5px;
-float:left;
-}
-
-#quick_login .register a{
-	font-size: 10px
-}
-#quick_login div.form div.fields{
-padding-top: 2px;
-padding-left:10px;
-}
-
-#quick_login div.form div.fields div.field{
- padding: 5px;
-}
-
-#quick_login div.form div.fields div.field div.label label{
-color:#fff;
-padding-bottom: 3px;
+    min-height: 80px;
+    margin: 37px 0 0 -251px;
+    padding: 4px;
+    position: absolute;
+    width: 278px;
+    
+    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 );
+
+	z-index: 999;
+	-webkit-border-radius: 0px 0px 4px 4px;
+	-khtml-border-radius: 0px 0px 4px 4px;
+	-moz-border-radius: 0px 0px 4px 4px;
+	border-radius: 0px 0px 4px 4px;
+	box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
+}
+#quick_login h4{
+    color: #fff;
+    padding: 5px 0px 5px 14px;
+}
+
+#quick_login .password_forgoten {
+	padding-right: 10px;
+	padding-top: 0px;
+	text-align: left;
+}
+
+#quick_login .password_forgoten a {
+	font-size: 10px;
+	color: #fff;
+}
+
+#quick_login .register {
+	padding-right: 10px;
+	padding-top: 5px;
+	text-align: left;
+}
+
+#quick_login .register a {
+	font-size: 10px;
+	color: #fff;
+}
+
+#quick_login .submit {
+    margin: -20px 0 0 0px;
+    position: absolute;
+    right: 15px;
+}
+
+#quick_login .links_left{
+	float: left;
+}
+#quick_login .links_right{
+    float: right;
+}
+#quick_login .full_name{
+    color: #FFFFFF;
+    font-weight: bold;
+    padding: 3px;
+}
+#quick_login .big_gravatar{
+	padding:4px 0px 0px 6px;
+}
+#quick_login .inbox{
+    padding:4px 0px 0px 6px;
+    color: #FFFFFF;
+    font-weight: bold;    
+}
+#quick_login .inbox a{
+	color: #FFFFFF;
+}
+#quick_login .email,#quick_login .email a{
+    color: #FFFFFF;
+    padding: 3px;
+    
+}
+#quick_login .links .logout{
+
+}
+
+#quick_login div.form div.fields {
+	padding-top: 2px;
+	padding-left: 10px;
+}
+
+#quick_login div.form div.fields div.field {
+	padding: 5px;
+}
+
+#quick_login div.form div.fields div.field div.label label {
+	color: #fff;
+	padding-bottom: 3px;
 }
 
 #quick_login div.form div.fields div.field div.input input {
-width:236px;
-background:#FFF;
-border-top:1px solid #b3b3b3;
-border-left:1px solid #b3b3b3;
-border-right:1px solid #eaeaea;
-border-bottom:1px solid #eaeaea;
-color:#000;
-font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
-font-size:11px;
-margin:0;
-padding:5px 7px 4px;
+	width: 236px;
+	background: #FFF;
+	border-top: 1px solid #b3b3b3;
+	border-left: 1px solid #b3b3b3;
+	border-right: 1px solid #eaeaea;
+	border-bottom: 1px solid #eaeaea;
+	color: #000;
+	font-size: 11px;
+	margin: 0;
+	padding: 5px 7px 4px;
 }
 
 #quick_login div.form div.fields div.buttons {
-clear:both;
-overflow:hidden;
-text-align:right;
-margin:0;
-padding:10px 14px 0px 5px;
+	clear: both;
+	overflow: hidden;
+	text-align: right;
+	margin: 0;
+	padding: 5px 14px 0px 5px;
 }
 
 #quick_login div.form div.links {
-clear:both;
-overflow:hidden;
-margin:10px 0 0;
-padding:0 0 2px;
+	clear: both;
+	overflow: hidden;
+	margin: 10px 0 0;
+	padding: 0 0 2px;
+}
+
+#quick_login ol.links{
+    display: block;
+    font-weight: bold;
+    list-style: none outside none;
+    text-align: right;
+}
+#quick_login ol.links li{
+    line-height: 27px;
+    margin: 0;
+    padding: 0;
+    color: #fff;
+    display: block;
+    float:none !important;
+}
+
+#quick_login ol.links li a{
+    color: #fff;
+    display: block;
+    padding: 2px;
+}
+#quick_login ol.links li a:HOVER{
+    background-color: inherit !important;
 }
 
 #register div.title {
-clear:both;
-overflow:hidden;
-position:relative;
-background:#003367 url("../images/header_inner.png") repeat-x;
-margin:0 auto;
-padding:0;
+	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-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 );
+	margin: 0 auto;
+	padding: 0;
 }
 
 #register div.inner {
-background:#FFF;
-border-top:none;
-border-bottom:none;
-margin:0 auto;
-padding:20px;
+	background: #FFF;
+	border-top: none;
+	border-bottom: none;
+	margin: 0 auto;
+	padding: 20px;
 }
 
 #register div.form div.fields div.field div.label {
-width:135px;
-float:left;
-text-align:right;
-margin:2px 10px 0 0;
-padding:5px 0 0 5px;
+	width: 135px;
+	float: left;
+	text-align: right;
+	margin: 2px 10px 0 0;
+	padding: 5px 0 0 5px;
 }
 
 #register div.form div.fields div.field div.input input {
-width:300px;
-background:#FFF;
-border-top:1px solid #b3b3b3;
-border-left:1px solid #b3b3b3;
-border-right:1px solid #eaeaea;
-border-bottom:1px solid #eaeaea;
-color:#000;
-font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
-font-size:11px;
-margin:0;
-padding:7px 7px 6px;
+	width: 300px;
+	background: #FFF;
+	border-top: 1px solid #b3b3b3;
+	border-left: 1px solid #b3b3b3;
+	border-right: 1px solid #eaeaea;
+	border-bottom: 1px solid #eaeaea;
+	color: #000;
+	font-size: 11px;
+	margin: 0;
+	padding: 7px 7px 6px;
 }
 
 #register div.form div.fields div.buttons {
-clear:both;
-overflow:hidden;
-border-top:1px solid #DDD;
-text-align:left;
-margin:0;
-padding:10px 0 0 150px;
-}
-
+	clear: both;
+	overflow: hidden;
+	border-top: 1px solid #DDD;
+	text-align: left;
+	margin: 0;
+	padding: 10px 0 0 150px;
+}
 
 #register div.form div.activation_msg {
-padding-top:4px;
-padding-bottom:4px;
-}
-
-#journal .journal_day{
-font-size:20px;
-padding:10px 0px;
-border-bottom:2px solid #DDD;
-margin-left:10px;
-margin-right:10px;
-}
-
-#journal .journal_container{
-padding:5px;
-clear:both;
-margin:0px 5px 0px 10px;
-}
-
-#journal .journal_action_container{
-padding-left:38px;
-}
-
-#journal .journal_user{
-color: #747474;
-font-size: 14px;
-font-weight: bold;
-height: 30px;
-}
-#journal .journal_icon{
-clear: both;
-float: left;
-padding-right: 4px;
-padding-top: 3px;
-}
-#journal .journal_action{
-padding-top:4px;
-min-height:2px;
-float:left
-}
-#journal .journal_action_params{
-clear: left;
-padding-left: 22px;
-}
-#journal .journal_repo{
-float: left;
-margin-left: 6px;
-padding-top: 3px;
-}
-#journal .date{
-clear: both;
-color: #777777;
-font-size: 11px;
-padding-left: 22px;
-}
-#journal .journal_repo .journal_repo_name{
-font-weight: bold;
-font-size: 1.1em;
-}
-#journal .compare_view{
-padding: 5px 0px 5px 0px;
-width: 95px;
-}
-.journal_highlight{
-font-weight: bold;
-padding: 0 2px;
-vertical-align: bottom;
-}
+	padding-top: 4px;
+	padding-bottom: 4px;
+}
+
+#journal .journal_day {
+	font-size: 20px;
+	padding: 10px 0px;
+	border-bottom: 2px solid #DDD;
+	margin-left: 10px;
+	margin-right: 10px;
+}
+
+#journal .journal_container {
+	padding: 5px;
+	clear: both;
+	margin: 0px 5px 0px 10px;
+}
+
+#journal .journal_action_container {
+	padding-left: 38px;
+}
+
+#journal .journal_user {
+	color: #747474;
+	font-size: 14px;
+	font-weight: bold;
+	height: 30px;
+}
+
+#journal .journal_icon {
+	clear: both;
+	float: left;
+	padding-right: 4px;
+	padding-top: 3px;
+}
+
+#journal .journal_action {
+	padding-top: 4px;
+	min-height: 2px;
+	float: left
+}
+
+#journal .journal_action_params {
+	clear: left;
+	padding-left: 22px;
+}
+
+#journal .journal_repo {
+	float: left;
+	margin-left: 6px;
+	padding-top: 3px;
+}
+
+#journal .date {
+	clear: both;
+	color: #777777;
+	font-size: 11px;
+	padding-left: 22px;
+}
+
+#journal .journal_repo .journal_repo_name {
+	font-weight: bold;
+	font-size: 1.1em;
+}
+
+#journal .compare_view {
+	padding: 5px 0px 5px 0px;
+	width: 95px;
+}
+
+.journal_highlight {
+	font-weight: bold;
+	padding: 0 2px;
+	vertical-align: bottom;
+}
+
 .trending_language_tbl,.trending_language_tbl td {
-border:0 !important;
-margin:0 !important;
-padding:0 !important;
+	border: 0 !important;
+	margin: 0 !important;
+	padding: 0 !important;
+}
+
+.trending_language_tbl,.trending_language_tbl tr {
+    border-spacing: 1px;
 }
 
 .trending_language {
-background-color:#003367;
-color:#FFF;
-display:block;
-min-width:20px;
-text-decoration:none;
-height:12px;
-margin-bottom:4px;
-margin-left:5px;
-white-space:pre;
-padding:3px;
+	background-color: #003367;
+	color: #FFF;
+	display: block;
+	min-width: 20px;
+	text-decoration: none;
+	height: 12px;
+	margin-bottom: 0px;
+	margin-left: 5px;
+	white-space: pre;
+	padding: 3px;
 }
 
 h3.files_location {
-font-size:1.8em;
-font-weight:700;
-border-bottom:none !important;
-margin:10px 0 !important;
+	font-size: 1.8em;
+	font-weight: 700;
+	border-bottom: none !important;
+	margin: 10px 0 !important;
 }
 
 #files_data dl dt {
-float:left;
-width:115px;
-margin:0 !important;
-padding:5px;
+	float: left;
+	width: 60px;
+	margin: 0 !important;
+	padding: 5px;
 }
 
 #files_data dl dd {
-margin:0 !important;
-padding:5px !important;
+	margin: 0 !important;
+	padding: 5px !important;
+}
+
+.tablerow0 {
+	background-color: #F8F8F8;
+}
+
+.tablerow1 {
+    background-color: #FFFFFF;
+}
+
+.changeset_id {
+	font-family: monospace;
+	color: #666666;
+}
+
+.changeset_hash {
+	color: #000000;
 }
 
 #changeset_content {
-border:1px solid #CCC;
-padding:5px;
-}
-#changeset_compare_view_content{
-border:1px solid #CCC;
-padding:5px;
+	border-left: 1px solid #CCC;
+	border-right: 1px solid #CCC;
+	border-bottom: 1px solid #CCC;
+	padding: 5px;
+}
+
+#changeset_compare_view_content {
+	border: 1px solid #CCC;
+	padding: 5px;
 }
 
 #changeset_content .container {
-min-height:120px;
-font-size:1.2em;
-overflow:hidden;
-}
-
-#changeset_compare_view_content .compare_view_commits{
-width: auto !important;
-}
-
-#changeset_compare_view_content .compare_view_commits td{
-padding:0px 0px 0px 12px !important;
+	min-height: 100px;
+	font-size: 1.2em;
+	overflow: hidden;
+}
+
+#changeset_compare_view_content .compare_view_commits {
+	width: auto !important;
+}
+
+#changeset_compare_view_content .compare_view_commits td {
+	padding: 0px 0px 0px 12px !important;
 }
 
 #changeset_content .container .right {
-float:right;
-width:25%;
-text-align:right;
+	float: right;
+	width: 20%;
+	text-align: right;
 }
 
 #changeset_content .container .left .message {
-font-style:italic;
-color:#556CB5;
-white-space:pre-wrap;
-}
-
-.cs_files .cur_cs{
-margin:10px 2px;
-font-weight: bold;
-}
-
-.cs_files .node{
-float: left;
-}
-.cs_files .changes{
-float: right;
-}
-.cs_files .changes .added{
-background-color: #BBFFBB;
-float: left;
-text-align: center;
-font-size: 90%; 
-}
-.cs_files .changes .deleted{
-background-color: #FF8888;
-float: left;
-text-align: center;
-font-size: 90%;
-}
+	white-space: pre-wrap;
+}
+#changeset_content .container .left .message a:hover {
+	text-decoration: none;
+}
+.cs_files .cur_cs {
+	margin: 10px 2px;
+	font-weight: bold;
+}
+
+.cs_files .node {
+	float: left;
+}
+
+.cs_files .changes {
+	float: right;
+	color:#003367;
+	
+}
+
+.cs_files .changes .added {
+	background-color: #BBFFBB;
+	float: left;
+	text-align: center;
+	font-size: 9px;
+    padding: 2px 0px 2px 0px;
+}
+
+.cs_files .changes .deleted {
+	background-color: #FF8888;
+	float: left;
+	text-align: center;
+	font-size: 9px;
+    padding: 2px 0px 2px 0px;
+}
+
 .cs_files .cs_added {
-background:url("../images/icons/page_white_add.png") no-repeat scroll 3px;
-height:16px;
-padding-left:20px;
-margin-top:7px;
-text-align:left;
+	background: url("../images/icons/page_white_add.png") no-repeat scroll
+		3px;
+	height: 16px;
+	padding-left: 20px;
+	margin-top: 7px;
+	text-align: left;
 }
 
 .cs_files .cs_changed {
-background:url("../images/icons/page_white_edit.png") no-repeat scroll 3px;
-height:16px;
-padding-left:20px;
-margin-top:7px;
-text-align:left;
+	background: url("../images/icons/page_white_edit.png") no-repeat scroll
+		3px;
+	height: 16px;
+	padding-left: 20px;
+	margin-top: 7px;
+	text-align: left;
 }
 
 .cs_files .cs_removed {
-background:url("../images/icons/page_white_delete.png") no-repeat scroll 3px;
-height:16px;
-padding-left:20px;
-margin-top:7px;
-text-align:left;
+	background: url("../images/icons/page_white_delete.png") no-repeat
+		scroll 3px;
+	height: 16px;
+	padding-left: 20px;
+	margin-top: 7px;
+	text-align: left;
 }
 
 #graph {
-overflow:hidden;
+	overflow: hidden;
 }
 
 #graph_nodes {
-float: left;
-margin-right: -6px;
-margin-top: -4px;
+	float: left;
+	margin-right: -6px;
+	margin-top: 0px;
 }
 
 #graph_content {
-width:800px;
-float:left;
-
+	width: 80%;
+	float: left;
 }
 
 #graph_content .container_header {
-border:1px solid #CCC;
-padding:10px;
-}
-#graph_content #rev_range_container{
-padding:10px 0px;
-}
+	border-bottom: 1px solid #DDD;
+	padding: 10px;
+	height: 25px;
+}
+
+#graph_content #rev_range_container {
+	padding: 7px 20px;
+	float: left;
+}
+
 #graph_content .container {
-border-bottom:1px solid #CCC;
-border-left:1px solid #CCC;
-border-right:1px solid #CCC;
-min-height:70px;
-overflow:hidden;
-font-size:1.2em;
+	border-bottom: 1px solid #DDD;
+	height: 56px;
+	overflow: hidden;
 }
 
 #graph_content .container .right {
-float:right;
-width:28%;
-text-align:right;
-padding-bottom:5px;
-}
+	float: right;
+	width: 23%;
+	text-align: right;
+}
+
+#graph_content .container .left {
+	float: left;
+	width: 25%;
+	padding-left: 5px;
+}
+
+#graph_content .container .mid {
+	float: left;
+	width: 49%;
+}
+
 
 #graph_content .container .left .date {
-font-weight:700;
-padding-bottom:5px;
-}
-#graph_content .container .left .date span{
-vertical-align: text-top;    
-}
-
-#graph_content .container .left .author{
-    height: 22px;
-}
-#graph_content .container .left .author .user{
-color: #444444;
-float: left;
-font-size: 12px;
-margin-left: -4px;
-margin-top: 4px;
-}
-
-#graph_content .container .left .message {
-font-size:100%;
-padding-top:3px;
-white-space:pre-wrap;
-}
-
-.right div {
-clear:both;
-}
-
-.right .changes .changed_total{
-border:1px solid #DDD;
-display:block;
-float:right;
-text-align:center;
-min-width:45px;
-cursor: pointer;
-background:#FD8;
-font-weight: bold;
-}
+	color: #666;
+	padding-left: 22px;
+	font-size: 10px;
+}
+
+#graph_content .container .left .author {
+	height: 22px;
+}
+
+#graph_content .container .left .author .user {
+	color: #444444;
+	float: left;
+	margin-left: -4px;
+	margin-top: 4px;
+}
+
+#graph_content .container .mid .message {
+	white-space: pre-wrap;
+}
+
+#graph_content .container .mid .message a:hover{
+	text-decoration: none;
+}
+#content #graph_content .message .revision-link,
+#changeset_content .container .message .revision-link
+ {
+	color:#3F6F9F;
+    font-weight: bold !important;
+}
+
+#content #graph_content .message .issue-tracker-link,
+#changeset_content .container .message .issue-tracker-link{
+    color:#3F6F9F;
+    font-weight: bold !important;
+}
+
+.right .comments-container{
+	padding-right: 5px;
+	margin-top:1px;
+	float:right;
+	height:14px;
+}
+
+.right .comments-cnt{
+    float: left;
+    color: rgb(136, 136, 136); 
+    padding-right: 2px; 
+}
+
+.right .changes{
+	clear: both;
+}
+
+.right .changes .changed_total {
+	display: block;
+	float: right;
+	text-align: center;
+	min-width: 45px;
+	cursor: pointer;
+	color: #444444;
+	background: #FEA;
+	-webkit-border-radius: 0px 0px 0px 6px;
+	-moz-border-radius: 0px 0px 0px 6px;
+	border-radius: 0px 0px 0px 6px;
+	padding: 1px;
+}
+
 .right .changes .added,.changed,.removed {
-border:1px solid #DDD;
-display:block;
-float:right;
-text-align:center;
-min-width:15px;
-cursor: help;
-}
-.right .changes .large {
-border:1px solid #DDD;
-display:block;
-float:right;
-text-align:center;
-min-width:45px;
-cursor: help;
-background: #54A9F7;
+	display: block;
+	padding: 1px;
+	color: #444444;
+	float: right;
+	text-align: center;
+	min-width: 15px;
 }
 
 .right .changes .added {
-background:#BFB;
+	background: #CFC;
 }
 
 .right .changes .changed {
-background:#FD8;
+	background: #FEA;
 }
 
 .right .changes .removed {
-background:#F88;
+	background: #FAA;
 }
 
 .right .merge {
-vertical-align:top;
-font-size:0.75em;
-font-weight:700;
+  padding: 1px 3px 1px 3px;
+  background-color: #fca062;
+  font-size: 10px;
+  font-weight: bold;
+  color: #ffffff;
+  text-transform: uppercase;
+  white-space: nowrap;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+  margin-right: 2px;
 }
 
 .right .parent {
-font-size:90%;
-font-family:monospace;
-}
-
-.right .logtags .branchtag {
-background:#FFF url("../images/icons/arrow_branch.png") no-repeat right 6px;
-display:block;
-font-size:0.8em;
-padding:11px 16px 0 0;
-}
-
-.right .logtags .tagtag {
-background:#FFF url("../images/icons/tag_blue.png") no-repeat right 6px;
-display:block;
-font-size:0.8em;
-padding:11px 16px 0 0;
-}
-
+	color: #666666;
+	clear:both;
+}
+.right .logtags{
+	padding: 2px 2px 2px 2px;
+}
+.right .logtags .branchtag,.logtags .branchtag {
+  padding: 1px 3px 1px 3px;
+  background-color: #bfbfbf;
+  font-size: 10px;
+  font-weight: bold;
+  color: #ffffff;
+  text-transform: uppercase;
+  white-space: nowrap;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+.right .logtags .branchtag a:hover,.logtags .branchtag a{
+	color: #ffffff;
+}
+.right .logtags .branchtag a:hover,.logtags .branchtag a:hover{
+	text-decoration: none;
+	color: #ffffff;
+}
+.right .logtags .tagtag,.logtags .tagtag {
+  padding: 1px 3px 1px 3px;
+  background-color: #62cffc;
+  font-size: 10px;
+  font-weight: bold;
+  color: #ffffff;
+  text-transform: uppercase;
+  white-space: nowrap;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+.right .logtags .tagtag a:hover,.logtags .tagtag a{
+	color: #ffffff;
+}
+.right .logtags .tagtag a:hover,.logtags .tagtag a:hover{
+    text-decoration: none;
+    color: #ffffff;
+}
+.right .logbooks .bookbook,.logbooks .bookbook {
+  padding: 1px 3px 2px;
+  background-color: #46A546;
+  font-size: 9.75px;
+  font-weight: bold;
+  color: #ffffff;
+  text-transform: uppercase;
+  white-space: nowrap;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+.right .logbooks .bookbook,.logbooks .bookbook a{
+	color: #ffffff;
+}
+.right .logbooks .bookbook,.logbooks .bookbook a:hover{
+    text-decoration: none;
+    color: #ffffff;
+}
 div.browserblock {
-overflow:hidden;
-border:1px solid #ccc;
-background:#f8f8f8;
-font-size:100%;
-line-height:125%;
-padding:0;
+	overflow: hidden;
+	border: 1px solid #ccc;
+	background: #f8f8f8;
+	font-size: 100%;
+	line-height: 125%;
+	padding: 0;
+    -webkit-border-radius: 6px 6px 0px 0px;
+    -moz-border-radius: 6px 6px 0px 0px;
+    border-radius: 6px 6px 0px 0px;	
 }
 
 div.browserblock .browser-header {
-background:#FFF;
-padding:10px 0px 15px 0px;
-width: 100%;
-}
+	background: #FFF;
+	padding: 10px 0px 15px 0px;
+	width: 100%;
+}
+
 div.browserblock .browser-nav {
-float:left
+	float: left
 }
 
 div.browserblock .browser-branch {
-float:left;
+	float: left;
 }
 
 div.browserblock .browser-branch label {
-color:#4A4A4A;
-vertical-align:text-top;
+	color: #4A4A4A;
+	vertical-align: text-top;
 }
 
 div.browserblock .browser-header span {
-margin-left:5px;
-font-weight:700;
-}
-
-div.browserblock .browser-search{
-	clear:both;
-	padding:8px 8px 0px 5px;
+	margin-left: 5px;
+	font-weight: 700;
+}
+
+div.browserblock .browser-search {
+	clear: both;
+	padding: 8px 8px 0px 5px;
 	height: 20px;
 }
+
 div.browserblock #node_filter_box {
-}
-
-div.browserblock .search_activate{
-    float: left
-}
-
-div.browserblock .add_node{
-    float: left;
-    padding-left: 5px;
-}
-
-div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover{
-    text-decoration: none !important;    
+	
+}
+
+div.browserblock .search_activate {
+	float: left
+}
+
+div.browserblock .add_node {
+	float: left;
+	padding-left: 5px;
+}
+
+div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover
+	{
+	text-decoration: none !important;
 }
 
 div.browserblock .browser-body {
-background:#EEE;
-border-top:1px solid #CCC;
+	background: #EEE;
+	border-top: 1px solid #CCC;
 }
 
 table.code-browser {
-border-collapse:collapse;
-width:100%;
+	border-collapse: collapse;
+	width: 100%;
 }
 
 table.code-browser tr {
-margin:3px;
+	margin: 3px;
 }
 
 table.code-browser thead th {
-background-color:#EEE;
-height:20px;
-font-size:1.1em;
-font-weight:700;
-text-align:left;
-padding-left:10px;
+	background-color: #EEE;
+	height: 20px;
+	font-size: 1.1em;
+	font-weight: 700;
+	text-align: left;
+	padding-left: 10px;
 }
 
 table.code-browser tbody td {
-padding-left:10px;
-height:20px;
+	padding-left: 10px;
+	height: 20px;
 }
 
 table.code-browser .browser-file {
-background:url("../images/icons/document_16.png") no-repeat scroll 3px;
-height:16px;
-padding-left:20px;
-text-align:left;
-}
-.diffblock .changeset_file{
-background:url("../images/icons/file.png") no-repeat scroll 3px;
-height:16px;
-padding-left:22px;
-text-align:left;
-font-size: 14px;
-}
-
-.diffblock .changeset_header{
-margin-left: 6px !important;
-}
-
+	background: url("../images/icons/document_16.png") no-repeat scroll 3px;
+	height: 16px;
+	padding-left: 20px;
+	text-align: left;
+}
+.diffblock .changeset_header {
+    height: 16px;
+}
+.diffblock .changeset_file {
+	background: url("../images/icons/file.png") no-repeat scroll 3px;
+	text-align: left;
+	float: left;
+	padding: 2px 0px 2px 22px;
+}
+.diffblock .diff-menu-wrapper{
+	float: left;
+}
+
+.diffblock .diff-menu{
+    position: absolute;
+    background: none repeat scroll 0 0 #FFFFFF;
+    border-color: #003367 #666666 #666666;
+    border-right: 1px solid #666666;
+    border-style: solid solid solid;
+    border-width: 1px;
+    box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
+    margin-top:5px;
+    margin-left:1px;
+    
+}
+.diffblock .diff-actions {
+    padding: 2px 0px 0px 2px;
+    float: left;
+}
+.diffblock  .diff-menu ul li {
+	padding: 0px 0px 0px 0px !important;
+}
+.diffblock  .diff-menu ul li a{
+	display: block;
+	padding: 3px 8px 3px 8px !important;
+}
+.diffblock  .diff-menu ul li a:hover{
+    text-decoration: none;
+    background-color: #EEEEEE;
+}
 table.code-browser .browser-dir {
-background:url("../images/icons/folder_16.png") no-repeat scroll 3px;
-height:16px;
-padding-left:20px;
-text-align:left;
+	background: url("../images/icons/folder_16.png") no-repeat scroll 3px;
+	height: 16px;
+	padding-left: 20px;
+	text-align: left;
 }
 
 .box .search {
+	clear: both;
+	overflow: hidden;
+	margin: 0;
+	padding: 0 20px 10px;
+}
+
+.box .search div.search_path {
+	background: none repeat scroll 0 0 #EEE;
+	border: 1px solid #CCC;
+	color: blue;
+	margin-bottom: 10px;
+	padding: 10px 0;
+}
+
+.box .search div.search_path div.link {
+	font-weight: 700;
+	margin-left: 25px;
+}
+
+.box .search div.search_path div.link a {
+	color: #003367;
+	cursor: pointer;
+	text-decoration: none;
+}
+
+#path_unlock {
+	color: red;
+	font-size: 1.2em;
+	padding-left: 4px;
+}
+
+.info_box span {
+	margin-left: 3px;
+	margin-right: 3px;
+}
+
+.info_box .rev {
+	color: #003367;
+	font-size: 1.6em;
+	font-weight: bold;
+	vertical-align: sub;
+}
+
+.info_box input#at_rev,.info_box input#size {
+	background: #FFF;
+	border-top: 1px solid #b3b3b3;
+	border-left: 1px solid #b3b3b3;
+	border-right: 1px solid #eaeaea;
+	border-bottom: 1px solid #eaeaea;
+	color: #000;
+	font-size: 12px;
+	margin: 0;
+	padding: 1px 5px 1px;
+}
+
+.info_box input#view {
+	text-align: center;
+	padding: 4px 3px 2px 2px;
+}
+
+.yui-overlay,.yui-panel-container {
+	visibility: hidden;
+	position: absolute;
+	z-index: 2;
+}
+
+.yui-tt {
+	visibility: hidden;
+	position: absolute;
+	color: #666;
+	background-color: #FFF;
+	border: 2px solid #003367;
+	font: 100% sans-serif;
+	width: auto;
+	opacity: 1px;
+	padding: 8px;
+	white-space: pre-wrap;
+	-webkit-border-radius: 8px 8px 8px 8px;
+	-khtml-border-radius: 8px 8px 8px 8px;
+	-moz-border-radius: 8px 8px 8px 8px;
+	border-radius: 8px 8px 8px 8px;
+	box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
+}
+
+.ac {
+	vertical-align: top;
+}
+
+.ac .yui-ac {
+	position: relative;
+	font-size: 100%;
+}
+
+.ac .perm_ac {
+	width: 15em;
+}
+
+.ac .yui-ac-input {
+	width: 100%;
+}
+
+.ac .yui-ac-container {
+	position: absolute;
+	top: 1.6em;
+	width: 100%;
+}
+
+.ac .yui-ac-content {
+	position: absolute;
+	width: 100%;
+	border: 1px solid gray;
+	background: #fff;
+	overflow: hidden;
+	z-index: 9050;
+}
+
+.ac .yui-ac-shadow {
+	position: absolute;
+	width: 100%;
+	background: #000;
+	-moz-opacity: 0.1px;
+	opacity: .10;
+	filter: alpha(opacity =     10);
+	z-index: 9049;
+	margin: .3em;
+}
+
+.ac .yui-ac-content ul {
+	width: 100%;
+	margin: 0;
+	padding: 0;
+}
+
+.ac .yui-ac-content li {
+	cursor: default;
+	white-space: nowrap;
+	margin: 0;
+	padding: 2px 5px;
+}
+
+.ac .yui-ac-content li.yui-ac-prehighlight {
+	background: #B3D4FF;
+}
+
+.ac .yui-ac-content li.yui-ac-highlight {
+	background: #556CB5;
+	color: #FFF;
+}
+
+.follow {
+	background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
+	height: 16px;
+	width: 20px;
+	cursor: pointer;
+	display: block;
+	float: right;
+	margin-top: 2px;
+}
+
+.following {
+	background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
+	height: 16px;
+	width: 20px;
+	cursor: pointer;
+	display: block;
+	float: right;
+	margin-top: 2px;
+}
+
+.currently_following {
+	padding-left: 10px;
+	padding-bottom: 5px;
+}
+
+.add_icon {
+	background: url("../images/icons/add.png") no-repeat scroll 3px;
+	padding-left: 20px;
+	padding-top: 0px;
+	text-align: left;
+}
+
+.edit_icon {
+	background: url("../images/icons/folder_edit.png") no-repeat scroll 3px;
+	padding-left: 20px;
+	padding-top: 0px;
+	text-align: left;
+}
+
+.delete_icon {
+	background: url("../images/icons/delete.png") no-repeat scroll 3px;
+	padding-left: 20px;
+	padding-top: 0px;
+	text-align: left;
+}
+
+.refresh_icon {
+	background: url("../images/icons/arrow_refresh.png") no-repeat scroll
+		3px;
+	padding-left: 20px;
+	padding-top: 0px;
+	text-align: left;
+}
+
+.pull_icon {
+	background: url("../images/icons/connect.png") no-repeat scroll 3px;
+	padding-left: 20px;
+	padding-top: 0px;
+	text-align: left;
+}
+
+.rss_icon {
+	background: url("../images/icons/rss_16.png") no-repeat scroll 3px;
+	padding-left: 20px;
+	padding-top: 4px;
+	text-align: left;
+	font-size: 8px
+}
+
+.atom_icon {
+	background: url("../images/icons/atom.png") no-repeat scroll 3px;
+	padding-left: 20px;
+	padding-top: 4px;
+	text-align: left;
+	font-size: 8px
+}
+
+.archive_icon {
+	background: url("../images/icons/compress.png") no-repeat scroll 3px;
+	padding-left: 20px;
+	text-align: left;
+	padding-top: 1px;
+}
+
+.start_following_icon {
+	background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
+	padding-left: 20px;
+	text-align: left;
+	padding-top: 0px;
+}
+
+.stop_following_icon {
+	background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
+	padding-left: 20px;
+	text-align: left;
+	padding-top: 0px;
+}
+
+.action_button {
+	border: 0;
+	display: inline;
+}
+
+.action_button:hover {
+	border: 0;
+	text-decoration: underline;
+	cursor: pointer;
+}
+
+#switch_repos {
+	position: absolute;
+	height: 25px;
+	z-index: 1;
+}
+
+#switch_repos select {
+	min-width: 150px;
+	max-height: 250px;
+	z-index: 1;
+}
+
+.breadcrumbs {
+	border: medium none;
+	color: #FFF;
+	float: left;
+	text-transform: uppercase;
+	font-weight: 700;
+	font-size: 14px;
+	margin: 0;
+	padding: 11px 0 11px 10px;
+}
+
+.breadcrumbs .hash {
+	text-transform: none;
+	color: #fff;
+}
+
+.breadcrumbs a {
+	color: #FFF;
+}
+
+.flash_msg {
+	
+}
+
+.flash_msg ul {
+	
+}
+
+.error_msg {
+	background-color: #c43c35;
+	background-repeat: repeat-x;
+	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-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 );
+	border-color: #c43c35 #c43c35 #882a25;
+}
+
+.warning_msg {
+	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: -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-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 );
+	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: -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-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 );
+	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: -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-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 );
+	border-color: #339bb9 #339bb9 #22697d;
+}
+
+.success_msg,.error_msg,.notice_msg,.warning_msg {
+	font-size: 12px;
+	font-weight: 700;
+	min-height: 14px;
+	line-height: 14px;
+	margin-bottom: 10px;
+	margin-top: 0;
+	display: block;
+	overflow: auto;
+	padding: 6px 10px 6px 10px;
+	border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+	position: relative;
+	color: #FFF;
+	border-width: 1px;
+	border-style: solid;
+	-webkit-border-radius: 4px;
+	-moz-border-radius: 4px;
+	border-radius: 4px;
+	-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
+	-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
+	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
+}
+
+#msg_close {
+	background: transparent url("../icons/cross_grey_small.png") no-repeat
+		scroll 0 0;
+	cursor: pointer;
+	height: 16px;
+	position: absolute;
+	right: 5px;
+	top: 5px;
+	width: 16px;
+}
+
+div#legend_container table,div#legend_choices table {
+	width: auto !important;
+}
+
+table#permissions_manage {
+	width: 0 !important;
+}
+
+table#permissions_manage span.private_repo_msg {
+	font-size: 0.8em;
+	opacity: 0.6px;
+}
+
+table#permissions_manage td.private_repo_msg {
+	font-size: 0.8em;
+}
+
+table#permissions_manage tr#add_perm_input td {
+	vertical-align: middle;
+}
+
+div.gravatar {
+	background-color: #FFF;
+	float: left;
+	margin-right: 0.7em;
+	padding: 1px 1px 1px 1px;
+    line-height:0;
+	-webkit-border-radius: 3px;
+	-khtml-border-radius: 3px;
+	-moz-border-radius: 3px;
+	border-radius: 3px;
+}
+
+div.gravatar img {
+	-webkit-border-radius: 2px;
+	-khtml-border-radius: 2px;
+	-moz-border-radius: 2px;
+	border-radius: 2px;
+}
+
+#header,#content,#footer {
+	min-width: 978px;
+}
+
+#content {
+	clear: both;
+	overflow: hidden;
+	padding: 54px 10px 14px 10px;
+}
+
+#content div.box div.title div.search {
+	
+	border-left: 1px solid #316293;
+}
+
+#content div.box div.title div.search div.input input {
+	border: 1px solid #316293;
+}
+
+.ui-btn{
+    color: #515151;
+    background-color: #DADADA;
+    background-repeat: repeat-x;
+    background-image: -khtml-gradient(linear, left top, left bottom, from(#F4F4F4),to(#DADADA) );
+    background-image: -moz-linear-gradient(top, #F4F4F4, #DADADA);
+    background-image: -ms-linear-gradient(top, #F4F4F4, #DADADA);
+    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #F4F4F4),color-stop(100%, #DADADA) );
+    background-image: -webkit-linear-gradient(top, #F4F4F4, #DADADA) );
+    background-image: -o-linear-gradient(top, #F4F4F4, #DADADA) );
+    background-image: linear-gradient(top, #F4F4F4, #DADADA);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F4F4F4', endColorstr='#DADADA', GradientType=0);
+    
+    border-top: 1px solid #DDD;
+    border-left: 1px solid #c6c6c6;
+    border-right: 1px solid #DDD;
+    border-bottom: 1px solid #c6c6c6;
+    color: #515151;
+    outline: none;
+    margin: 0px 3px 3px 0px;
+    -webkit-border-radius: 4px 4px 4px 4px !important;
+    -khtml-border-radius: 4px 4px 4px 4px !important;
+    -moz-border-radius: 4px 4px 4px 4px !important;
+    border-radius: 4px 4px 4px 4px !important;
+    cursor: pointer !important;
+	padding: 3px 3px 3px 3px;	
+	background-position: 0 -15px;
+
+}
+.ui-btn.xsmall{
+    padding: 1px 2px 1px 1px;
+}
+.ui-btn.clone{
+	padding: 5px 2px 6px 1px;
+	margin: 0px -4px 3px 0px;
+    -webkit-border-radius: 4px 0px 0px 4px !important;
+    -khtml-border-radius: 4px 0px 0px 4px !important;
+    -moz-border-radius: 4px 0px 0px 4px !important;
+    border-radius: 4px 0px 0px 4px !important;
+    width: 100px;
+    text-align: center;
+    float: left;
+    position: absolute;
+}
+.ui-btn:focus {
+  outline: none;
+}
+.ui-btn:hover{
+    background-position: 0 0px;
+    text-decoration: none;
+    color: #515151;
+    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important;
+}
+
+.ui-btn.red{
+  color:#fff;
+  background-color: #c43c35;
+  background-repeat: repeat-x;
+  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-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);
+  border-color: #c43c35 #c43c35 #882a25;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+}
+
+
+.ui-btn.blue{
+  background-color: #339bb9;
+  background-repeat: repeat-x;
+  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-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);
+  border-color: #339bb9 #339bb9 #22697d;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);    
+}
+
+.ui-btn.green{
+  background-color: #57a957;
+  background-repeat: repeat-x;
+  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-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);
+  border-color: #57a957 #57a957 #3d773d;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);	
+}
+
+ins,div.options a:hover {
+	text-decoration: none;
+}
+
+img,
+#header #header-inner #quick li a:hover span.normal,
+#header #header-inner #quick li ul li.last,
+#content div.box div.form div.fields div.field div.textarea table td table td a,
+#clone_url,
+#clone_url_id
+{
+	border: none;
+}
+
+img.icon,.right .merge img {
+	vertical-align: bottom;
+}
+
+#header ul#logged-user,#content div.box div.title ul.links,
+#content div.box div.message div.dismiss,
+#content div.box div.traffic div.legend ul
+	{
+	float: right;
+	margin: 0;
+	padding: 0;
+}
+
+#header #header-inner #home,#header #header-inner #logo,
+#content div.box ul.left,#content div.box ol.left,
+#content div.box div.pagination-left,div#commit_history,
+div#legend_data,div#legend_container,div#legend_choices
+	{
+	float: left;
+}
+
+#header #header-inner #quick li:hover ul ul,
+#header #header-inner #quick li:hover ul ul ul,
+#header #header-inner #quick li:hover ul ul ul ul,
+#content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow
+	{
+	display: none;
+}
+
+#header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded
+	{
+	display: block;
+}
+
+#content div.graph {
+	padding: 0 10px 10px;
+}
+
+#content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a
+	{
+	color: #bfe3ff;
+}
+
+#content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal
+	{
+	margin: 10px 24px 10px 44px;
+}
+
+#content div.box div.form,#content div.box div.table,#content div.box div.traffic
+	{
+	clear: both;
+	overflow: hidden;
+	margin: 0;
+	padding: 0 20px 10px;
+}
+
+#content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields
+	{
+	clear: both;
+	overflow: hidden;
+	margin: 0;
+	padding: 0;
+}
+
+#content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span
+	{
+	height: 1%;
+	display: block;
+	color: #363636;
+	margin: 0;
+	padding: 2px 0 0;
+}
+
+#content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error
+	{
+	background: #FBE3E4;
+	border-top: 1px solid #e1b2b3;
+	border-left: 1px solid #e1b2b3;
+	border-right: 1px solid #FBC2C4;
+	border-bottom: 1px solid #FBC2C4;
+}
+
+#content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success
+	{
+	background: #E6EFC2;
+	border-top: 1px solid #cebb98;
+	border-left: 1px solid #cebb98;
+	border-right: 1px solid #c6d880;
+	border-bottom: 1px solid #c6d880;
+}
+
+#content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input
+	{
+	margin: 0;
+}
+
+#content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios
+	{
+	margin: 0 0 0 0px !important;
+	padding: 0;
+}
+
+#content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios
+	{
+	margin: 0 0 0 200px;
+	padding: 0;
+}
+
+#content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover
+	{
+	color: #000;
+	text-decoration: none;
+}
+
+#content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus
+	{
+	border: 1px solid #666;
+}
+
+#content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio
+	{
+	clear: both;
+	overflow: hidden;
+	margin: 0;
+	padding: 8px 0 2px;
+}
+
+#content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input
+	{
+	float: left;
+	margin: 0;
+}
+
+#content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label
+	{
+	height: 1%;
+	display: block;
+	float: left;
+	margin: 2px 0 0 4px;
+}
+
+div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input
+	{
+	color: #000;
+	font-size: 11px;
+	font-weight: 700;
+	margin: 0;
+}
+
+input.ui-button {
+	background: #e5e3e3 url("../images/button.png") repeat-x;
+	border-top: 1px solid #DDD;
+	border-left: 1px solid #c6c6c6;
+	border-right: 1px solid #DDD;
+	border-bottom: 1px solid #c6c6c6;
+	color: #515151 !important;
+	outline: none;
+	margin: 0;
+	padding: 6px 12px;
+	-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;
+	box-shadow: 0 1px 0 #ececec;
+	cursor: pointer;
+}
+
+input.ui-button:hover {
+	background: #b4b4b4 url("../images/button_selected.png") repeat-x;
+	border-top: 1px solid #ccc;
+	border-left: 1px solid #bebebe;
+	border-right: 1px solid #b1b1b1;
+	border-bottom: 1px solid #afafaf;
+}
+
+div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight
+	{
+	display: inline;
+}
+
+#content div.box div.form div.fields div.buttons,div.form div.fields div.buttons
+	{
+	margin: 10px 0 0 200px;
+	padding: 0;
+}
+
+#content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons
+	{
+	margin: 10px 0 0;
+}
+
+#content div.box table td.user,#content div.box table td.address {
+	width: 10%;
+	text-align: center;
+}
+
+#content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link
+	{
+	text-align: right;
+	margin: 6px 0 0;
+	padding: 0;
+}
+
+#content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover
+	{
+	background: #b4b4b4 url("../images/button_selected.png") repeat-x;
+	border-top: 1px solid #ccc;
+	border-left: 1px solid #bebebe;
+	border-right: 1px solid #b1b1b1;
+	border-bottom: 1px solid #afafaf;
+	color: #515151;
+	margin: 0;
+	padding: 6px 12px;
+}
+
+#content div.box div.pagination div.results,#content div.box div.pagination-wh div.results
+	{
+	text-align: left;
+	float: left;
+	margin: 0;
+	padding: 0;
+}
+
+#content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span
+	{
+	height: 1%;
+	display: block;
+	float: left;
+	background: #ebebeb url("../images/pager.png") repeat-x;
+	border-top: 1px solid #dedede;
+	border-left: 1px solid #cfcfcf;
+	border-right: 1px solid #c4c4c4;
+	border-bottom: 1px solid #c4c4c4;
+	color: #4A4A4A;
+	font-weight: 700;
+	margin: 0;
+	padding: 6px 8px;
+}
+
+#content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled
+	{
+	color: #B4B4B4;
+	padding: 6px;
+}
+
+#login,#register {
+	width: 520px;
+	margin: 10% auto 0;
+	padding: 0;
+}
+
+#login div.color,#register div.color {
+	clear: both;
+	overflow: hidden;
+	background: #FFF;
+	margin: 10px auto 0;
+	padding: 3px 3px 3px 0;
+}
+
+#login div.color a,#register div.color a {
+	width: 20px;
+	height: 20px;
+	display: block;
+	float: left;
+	margin: 0 0 0 3px;
+	padding: 0;
+}
+
+#login div.title h5,#register div.title h5 {
+	color: #fff;
+	margin: 10px;
+	padding: 0;
+}
+
+#login div.form div.fields div.field,#register div.form div.fields div.field
+	{
+	clear: both;
+	overflow: hidden;
+	margin: 0;
+	padding: 0 0 10px;
+}
+
+#login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message
+	{
+	height: 1%;
+	display: block;
+	color: red;
+	margin: 8px 0 0;
+	padding: 0;
+	max-width: 320px;
+}
+
+#login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label
+	{
+	color: #000;
+	font-weight: 700;
+}
+
+#login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input
+	{
+	float: left;
+	margin: 0;
+	padding: 0;
+}
+
+#login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox
+	{
+	margin: 0 0 0 184px;
+	padding: 0;
+}
+
+#login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label
+	{
+	color: #565656;
+	font-weight: 700;
+}
+
+#login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input
+	{
+	color: #000;
+	font-size: 1em;
+	font-weight: 700;
+	margin: 0;
+}
+
+#changeset_content .container .wrapper,#graph_content .container .wrapper
+	{
+	width: 600px;
+}
+
+#changeset_content .container .left {
+	float: left;
+	width: 75%;
+	padding-left: 5px;
+}
+
+#changeset_content .container .left .date,.ac .match {
+	font-weight: 700;
+	padding-top: 5px;
+	padding-bottom: 5px;
+}
+
+div#legend_container table td,div#legend_choices table td {
+	border: none !important;
+	height: 20px !important;
+	padding: 0 !important;
+}
+
+.q_filter_box {
+	-webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
+	-webkit-border-radius: 4px;
+	-moz-border-radius: 4px;
+	border-radius: 4px;
+    border: 0 none;
+    color: #AAAAAA;
+    margin-bottom: -4px;
+    margin-top: -4px;
+    padding-left: 3px;		
+}
+
+#node_filter {
+	border: 0px solid #545454;
+	color: #AAAAAA;
+	padding-left: 3px;
+}
+
+
+.group_members_wrap{
+	
+}
+
+.group_members .group_member{
+	height: 30px;
+	padding:0px 0px 0px 10px;
+}
+
+/*README STYLE*/
+
+div.readme {
+	padding:0px;
+}
+
+div.readme h2 {
+    font-weight: normal;
+}
+
+div.readme .readme_box {
+    background-color: #fafafa;
+}
+
+div.readme .readme_box {
 clear:both;
 overflow:hidden;
 margin:0;
 padding:0 20px 10px;
 }
 
-.box .search div.search_path {
-background:none repeat scroll 0 0 #EEE;
-border:1px solid #CCC;
-color:blue;
-margin-bottom:10px;
-padding:10px 0;
-}
-
-.box .search div.search_path div.link {
-font-weight:700;
-margin-left:25px;
-}
-
-.box .search div.search_path div.link a {
-color:#003367;
-cursor:pointer;
-text-decoration:none;
-}
-
-#path_unlock {
-color:red;
-font-size:1.2em;
-padding-left:4px;
-}
-
-.info_box span {
-margin-left:3px;
-margin-right:3px;
-}
-
-.info_box .rev {
-color: #003367;
-font-size: 1.6em;
-font-weight: bold;
-vertical-align: sub;
-}
-
-
-.info_box input#at_rev,.info_box input#size {
-background:#FFF;
-border-top:1px solid #b3b3b3;
-border-left:1px solid #b3b3b3;
-border-right:1px solid #eaeaea;
-border-bottom:1px solid #eaeaea;
-color:#000;
-font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
-font-size:12px;
-margin:0;
-padding:1px 5px 1px;
-}
-
-.info_box input#view {
-text-align:center;
-padding:4px 3px 2px 2px;
-}
-
-.yui-overlay,.yui-panel-container {
-visibility:hidden;
-position:absolute;
-z-index:2;
-}
-
-.yui-tt {
-visibility:hidden;
-position:absolute;
-color:#666;
-background-color:#FFF;
-font-family:arial, helvetica, verdana, sans-serif;
-border:2px solid #003367;
-font:100% sans-serif;
-width:auto;
-opacity:1px;
-padding:8px;
-white-space: pre-wrap;
--webkit-border-radius: 8px 8px 8px 8px;
--khtml-border-radius: 8px 8px 8px 8px; 
--moz-border-radius: 8px 8px 8px 8px;
-border-radius: 8px 8px 8px 8px;
-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-}
-
-.ac {
-vertical-align:top;
-}
-
-.ac .yui-ac {
-position:relative;
-font-family:arial;
-font-size:100%;
-}
-
-.ac .perm_ac {
-width:15em;
-}
-
-.ac .yui-ac-input {
-width:100%;
-}
-
-.ac .yui-ac-container {
-position:absolute;
-top:1.6em;
-width:100%;
-}
-
-.ac .yui-ac-content {
-position:absolute;
-width:100%;
-border:1px solid gray;
-background:#fff;
-overflow:hidden;
-z-index:9050;
-}
-
-.ac .yui-ac-shadow {
-position:absolute;
-width:100%;
-background:#000;
--moz-opacity:0.1px;
-opacity:.10;
-filter:alpha(opacity =   10);
-z-index:9049;
-margin:.3em;
-}
-
-.ac .yui-ac-content ul {
-width:100%;
-margin:0;
-padding:0;
-}
-
-.ac .yui-ac-content li {
-cursor:default;
-white-space:nowrap;
-margin:0;
-padding:2px 5px;
-}
-
-.ac .yui-ac-content li.yui-ac-prehighlight {
-background:#B3D4FF;
-}
-
-.ac .yui-ac-content li.yui-ac-highlight {
-background:#556CB5;
-color:#FFF;
-}
-
-
-.follow{
-background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
-height: 16px;
-width: 20px;
-cursor: pointer;
-display: block;
-float: right;
-margin-top: 2px;
-}
-
-.following{
-background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
-height: 16px;
-width: 20px;
-cursor: pointer;
-display: block;
-float: right;
-margin-top: 2px;
-}
-
-.currently_following{
-padding-left: 10px;
-padding-bottom:5px;
-}
-
-.add_icon {
-background:url("../images/icons/add.png") no-repeat scroll 3px;
-padding-left:20px;
-padding-top:0px;
-text-align:left;
-}
-
-.edit_icon {
-background:url("../images/icons/folder_edit.png") no-repeat scroll 3px;
-padding-left:20px;
-padding-top:0px;
-text-align:left;
-}
-
-.delete_icon {
-background:url("../images/icons/delete.png") no-repeat scroll 3px;
-padding-left:20px;
-padding-top:0px;
-text-align:left;
-}
-
-.refresh_icon {
-background:url("../images/icons/arrow_refresh.png") no-repeat scroll 3px;
-padding-left:20px;
-padding-top:0px;
-text-align:left;
-}
-
-.pull_icon {
-background:url("../images/icons/connect.png") no-repeat scroll 3px;
-padding-left:20px;
-padding-top:0px;
-text-align:left;
-}
-
-.rss_icon {
-background:url("../images/icons/rss_16.png") no-repeat scroll 3px;
-padding-left:20px;
-padding-top:0px;
-text-align:left;
-}
-
-.atom_icon {
-background:url("../images/icons/atom.png") no-repeat scroll 3px;
-padding-left:20px;
-padding-top:0px;
-text-align:left;
-}
-
-.archive_icon {
-background:url("../images/icons/compress.png") no-repeat scroll 3px;
-padding-left:20px;
-text-align:left;
-padding-top:1px;
-}
-
-.start_following_icon {
-background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
-padding-left:20px;
-text-align:left;
-padding-top:0px;
-}
-
-.stop_following_icon {
-background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
-padding-left:20px;
-text-align:left;
-padding-top:0px;
-}
-
-.action_button {
-border:0;
-display:inline;
-}
-
-.action_button:hover {
-border:0;
-text-decoration:underline;
-cursor:pointer;
-}
-
-#switch_repos {
-position:absolute;
-height:25px;
-z-index:1;
-}
-
-#switch_repos select {
-min-width:150px;
-max-height:250px;
-z-index:1;
-}
-
-.breadcrumbs {
-border:medium none;
-color:#FFF;
-float:left;
-text-transform:uppercase;
-font-weight:700;
-font-size:14px;
-margin:0;
-padding:11px 0 11px 10px;
-}
-
-.breadcrumbs a {
-color:#FFF;
-}
-
-.flash_msg ul {
-margin:0;
-padding:0 0 10px;
-}
-
-.error_msg {
-background-color:#FFCFCF;
-background-image:url("../images/icons/error_msg.png");
-border:1px solid #FF9595;
-color:#C30;
-}
-
-.warning_msg {
-background-color:#FFFBCC;
-background-image:url("../images/icons/warning_msg.png");
-border:1px solid #FFF35E;
-color:#C69E00;
-}
-
-.success_msg {
-background-color:#D5FFCF;
-background-image:url("../images/icons/success_msg.png");
-border:1px solid #97FF88;
-color:#090;
-}
-
-.notice_msg {
-background-color:#DCE3FF;
-background-image:url("../images/icons/notice_msg.png");
-border:1px solid #93A8FF;
-color:#556CB5;
-}
-
-.success_msg,.error_msg,.notice_msg,.warning_msg {
-background-position:10px center;
-background-repeat:no-repeat;
-font-size:12px;
-font-weight:700;
-min-height:14px;
-line-height:14px;
-margin-bottom:0;
-margin-top:0;
-display:block;
-overflow:auto;
-padding:6px 10px 6px 40px;
-}
-
-#msg_close {
-background:transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
-cursor:pointer;
-height:16px;
-position:absolute;
-right:5px;
-top:5px;
-width:16px;
-}
-
-div#legend_container table,div#legend_choices table {
-width:auto !important;
-}
-
-table#permissions_manage {
-width:0 !important;
-}
-
-table#permissions_manage span.private_repo_msg {
-font-size:0.8em;
-opacity:0.6px;
-}
-
-table#permissions_manage td.private_repo_msg {
-font-size:0.8em;
-}
-
-table#permissions_manage tr#add_perm_input td {
-vertical-align:middle;
-}
-
-div.gravatar {
-background-color:#FFF;
-border:1px solid #D0D0D0;
-float:left;
-margin-right:0.7em;
-padding:2px 2px 0;
-
--webkit-border-radius: 6px;
--khtml-border-radius: 6px; 
--moz-border-radius: 6px;
-border-radius: 6px;
-
-}
-
-div.gravatar img {
--webkit-border-radius: 4px;
--khtml-border-radius: 4px; 
--moz-border-radius: 4px;
-border-radius: 4px;	
-}
-
-#header,#content,#footer {
-min-width:978px;
-}
-
-#content {
-clear:both;
-overflow:hidden;
-padding:14px 10px;
-}
-
-#content div.box div.title div.search {
-background:url("../images/title_link.png") no-repeat top left;
-border-left:1px solid #316293;
-}
-
-#content div.box div.title div.search div.input input {
-border:1px solid #316293;
-}
-
-.ui-button-small a:hover {
-	
-}
-input.ui-button-small,.ui-button-small {
-background:#e5e3e3 url("../images/button.png") repeat-x !important;
-border-top:1px solid #DDD !important;
-border-left:1px solid #c6c6c6 !important;
-border-right:1px solid #DDD !important;
-border-bottom:1px solid #c6c6c6 !important;
-color:#515151 !important;
-outline:none !important;
-margin:0 !important;
--webkit-border-radius: 4px 4px 4px 4px !important;
--khtml-border-radius: 4px 4px 4px 4px !important; 
--moz-border-radius: 4px 4px 4px 4px !important;
-border-radius: 4px 4px 4px 4px !important;
-box-shadow: 0 1px 0 #ececec !important;
-cursor: pointer !important;
-padding:0px 2px 1px 2px;
-}
-
-input.ui-button-small:hover,.ui-button-small:hover {
-background:#b4b4b4 url("../images/button_selected.png") repeat-x !important;
-border-top:1px solid #ccc !important;
-border-left:1px solid #bebebe !important;
-border-right:1px solid #b1b1b1 !important;
-border-bottom:1px solid #afafaf !important;
-text-decoration: none;
-}
-
-input.ui-button-small-blue,.ui-button-small-blue {
-background:#4e85bb url("../images/button_highlight.png") repeat-x;
-border-top:1px solid #5c91a4;
-border-left:1px solid #2a6f89;
-border-right:1px solid #2b7089;
-border-bottom:1px solid #1a6480;
-color:#fff;
--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;
-box-shadow: 0 1px 0 #ececec;
-cursor: pointer;
-padding:0px 2px 1px 2px;
-}
-
-input.ui-button-small-blue:hover {
-	
-}
-
-
-ins,div.options a:hover {
-text-decoration:none;
-}
-
-img,#header #header-inner #quick li a:hover span.normal,#header #header-inner #quick li ul li.last,#content div.box div.form div.fields div.field div.textarea table td table td a,#clone_url {
-border:none;
-}
-
-img.icon,.right .merge img {
-vertical-align:bottom;
-}
-
-#header ul#logged-user,#content div.box div.title ul.links,#content div.box div.message div.dismiss,#content div.box div.traffic div.legend ul {
-float:right;
-margin:0;
-padding:0;
-}
-
-
-#header #header-inner #home,#header #header-inner #logo,#content div.box ul.left,#content div.box ol.left,#content div.box div.pagination-left,div#commit_history,div#legend_data,div#legend_container,div#legend_choices {
-float:left;
-}
-
-#header #header-inner #quick li:hover ul ul,#header #header-inner #quick li:hover ul ul ul,#header #header-inner #quick li:hover ul ul ul ul,#content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow {
-display:none;
-}
-
-#header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded {
-display:block;
-}
-
-#content div.graph{
-padding:0 10px 10px;
-}
-
-#content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a {
-color:#bfe3ff;
-}
-
-#content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal {
-margin:10px 24px 10px 44px;
-}
-
-#content div.box div.form,#content div.box div.table,#content div.box div.traffic {
+div.readme .readme_box h1, div.readme .readme_box h2, div.readme .readme_box h3, div.readme .readme_box h4, div.readme .readme_box h5, div.readme .readme_box h6 {
+border-bottom: 0 !important;
+margin: 0 !important;
+padding: 0 !important;
+line-height: 1.5em !important;
+}
+
+
+div.readme .readme_box h1:first-child {
+padding-top: .25em !important;
+}
+
+div.readme .readme_box h2, div.readme .readme_box h3 {
+margin: 1em 0 !important;
+}
+
+div.readme .readme_box h2 {
+margin-top: 1.5em !important;
+border-top: 4px solid #e0e0e0 !important;
+padding-top: .5em !important;
+}
+
+div.readme .readme_box p {
+color: black !important;
+margin: 1em 0 !important;
+line-height: 1.5em !important;
+}
+
+div.readme .readme_box ul {
+list-style: disc !important;
+margin: 1em 0 1em 2em !important;
+}
+
+div.readme .readme_box ol {
+list-style: decimal;
+margin: 1em 0 1em 2em !important;
+}
+
+div.readme .readme_box pre, code {
+font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
+}
+
+div.readme .readme_box code {
+    font-size: 12px !important;
+    background-color: ghostWhite !important;
+    color: #444 !important;
+    padding: 0 .2em !important;
+    border: 1px solid #dedede !important;
+}
+
+div.readme .readme_box pre code {
+	padding: 0 !important;
+	font-size: 12px !important;
+	background-color: #eee !important;
+	border: none !important;
+}
+
+div.readme .readme_box pre {
+	margin: 1em 0;
+	font-size: 12px;
+	background-color: #eee;
+	border: 1px solid #ddd;
+	padding: 5px;
+	color: #444;
+	overflow: auto;
+	-webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
+	-webkit-border-radius: 3px;
+	-moz-border-radius: 3px;
+	border-radius: 3px;
+}
+
+
+/** RST STYLE **/
+
+
+div.rst-block {
+    padding:0px;
+}
+
+div.rst-block h2 {
+    font-weight: normal;
+}
+
+div.rst-block  {
+    background-color: #fafafa;
+}
+
+div.rst-block  {
 clear:both;
 overflow:hidden;
 margin:0;
 padding:0 20px 10px;
 }
 
-#content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields {
-clear:both;
-overflow:hidden;
-margin:0;
-padding:0;
-}
-
-#content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span {
-height:1%;
-display:block;
-color:#363636;
-margin:0;
-padding:2px 0 0;
-}
-
-#content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error {
-background:#FBE3E4;
-border-top:1px solid #e1b2b3;
-border-left:1px solid #e1b2b3;
-border-right:1px solid #FBC2C4;
-border-bottom:1px solid #FBC2C4;
-}
-
-#content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success {
-background:#E6EFC2;
-border-top:1px solid #cebb98;
-border-left:1px solid #cebb98;
-border-right:1px solid #c6d880;
-border-bottom:1px solid #c6d880;
-}
-
-#content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input {
-margin:0;
-}
-
-#content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios{
-margin:0 0 0 0px !important;
-padding:0;
-}
-
-#content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios {
-margin:0 0 0 200px;
-padding:0;
-}
-
-
-#content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover {
-color:#000;
-text-decoration:none;
-}
-
-#content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus {
-border:1px solid #666;
-}
-
-#content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio {
-clear:both;
-overflow:hidden;
-margin:0;
-padding:8px 0 2px;
-}
-
-#content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input {
-float:left;
-margin:0;
-}
-
-#content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label {
-height:1%;
-display:block;
-float:left;
-margin:2px 0 0 4px;
-}
-
-div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input {
-color:#000;
-font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
-font-size:11px;
-font-weight:700;
-margin:0;
-}
-
-input.ui-button {
-background:#e5e3e3 url("../images/button.png") repeat-x;
-border-top:1px solid #DDD;
-border-left:1px solid #c6c6c6;
-border-right:1px solid #DDD;
-border-bottom:1px solid #c6c6c6;
-color:#515151 !important;
-outline:none;
-margin:0;
-padding:6px 12px;
--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;
-box-shadow: 0 1px 0 #ececec;
-cursor: pointer;
-}
-
-input.ui-button:hover {
-background:#b4b4b4 url("../images/button_selected.png") repeat-x;
-border-top:1px solid #ccc;
-border-left:1px solid #bebebe;
-border-right:1px solid #b1b1b1;
-border-bottom:1px solid #afafaf;
-}
-
-div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight {
-display:inline;
-}
-
-#content div.box div.form div.fields div.buttons,div.form div.fields div.buttons {
-margin:10px 0 0 200px;
-padding:0;
-}
-
-#content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons {
-margin:10px 0 0;
-}
-
-#content div.box table td.user,#content div.box table td.address {
-width:10%;
-text-align:center;
-}
-
-#content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link {
-text-align:right;
-margin:6px 0 0;
-padding:0;
-}
-
-
-#content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover {
-background:#b4b4b4 url("../images/button_selected.png") repeat-x;
-border-top:1px solid #ccc;
-border-left:1px solid #bebebe;
-border-right:1px solid #b1b1b1;
-border-bottom:1px solid #afafaf;
-color:#515151;
-margin:0;
-padding:6px 12px;
-}
-
-#content div.box div.pagination div.results,#content div.box div.pagination-wh div.results {
-text-align:left;
-float:left;
-margin:0;
-padding:0;
-}
-
-#content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span {
-height:1%;
-display:block;
-float:left;
-background:#ebebeb url("../images/pager.png") repeat-x;
-border-top:1px solid #dedede;
-border-left:1px solid #cfcfcf;
-border-right:1px solid #c4c4c4;
-border-bottom:1px solid #c4c4c4;
-color:#4A4A4A;
-font-weight:700;
-margin:0;
-padding:6px 8px;
-}
-
-#content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled {
-color:#B4B4B4;
-padding:6px;
-}
-
-#login,#register {
-width:520px;
-margin:10% auto 0;
-padding:0;
-}
-
-#login div.color,#register div.color {
-clear:both;
-overflow:hidden;
-background:#FFF;
-margin:10px auto 0;
-padding:3px 3px 3px 0;
-}
-
-#login div.color a,#register div.color a {
-width:20px;
-height:20px;
-display:block;
-float:left;
-margin:0 0 0 3px;
-padding:0;
-}
-
-#login div.title h5,#register div.title h5 {
-color:#fff;
-margin:10px;
-padding:0;
-}
-
-#login div.form div.fields div.field,#register div.form div.fields div.field {
-clear:both;
-overflow:hidden;
-margin:0;
-padding:0 0 10px;
-}
-
-#login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message {
-height:1%;
-display:block;
-color:red;
-margin:8px 0 0;
-padding:0;
-max-width: 320px;
-}
-
-#login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label {
-color:#000;
-font-weight:700;
-}
-
-#login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input {
-float:left;
-margin:0;
-padding:0;
-}
-
-#login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox {
-margin:0 0 0 184px;
-padding:0;
-}
-
-#login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label {
-color:#565656;
-font-weight:700;
-}
-
-#login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input {
-color:#000;
-font-size:1em;
-font-weight:700;
-font-family:Verdana, Helvetica, Sans-Serif;
-margin:0;
-}
-
-#changeset_content .container .wrapper,#graph_content .container .wrapper {
-width:600px;
-}
-
-#changeset_content .container .left,#graph_content .container .left {
-float:left;
-width:70%;
-padding-left:5px;
-}
-
-#changeset_content .container .left .date,.ac .match {
-font-weight:700;
-padding-top: 5px;
-padding-bottom:5px;
-}
-
-div#legend_container table td,div#legend_choices table td {
-border:none !important;
-height:20px !important;
-padding:0 !important;
-}
-
-#q_filter{
-border:0 none;
-color:#AAAAAA;
-margin-bottom:-4px;
-margin-top:-4px;
-padding-left:3px;
-}
-
-#node_filter{
-border:0px solid #545454;
-color:#AAAAAA;
-padding-left:3px;
-}
+div.rst-block  h1, div.rst-block  h2, div.rst-block  h3, div.rst-block  h4, div.rst-block  h5, div.rst-block  h6 {
+border-bottom: 0 !important;
+margin: 0 !important;
+padding: 0 !important;
+line-height: 1.5em !important;
+}
+
+
+div.rst-block  h1:first-child {
+padding-top: .25em !important;
+}
+
+div.rst-block  h2, div.rst-block  h3 {
+margin: 1em 0 !important;
+}
+
+div.rst-block  h2 {
+margin-top: 1.5em !important;
+border-top: 4px solid #e0e0e0 !important;
+padding-top: .5em !important;
+}
+
+div.rst-block  p {
+color: black !important;
+margin: 1em 0 !important;
+line-height: 1.5em !important;
+}
+
+div.rst-block  ul {
+list-style: disc !important;
+margin: 1em 0 1em 2em !important;
+}
+
+div.rst-block  ol {
+list-style: decimal;
+margin: 1em 0 1em 2em !important;
+}
+
+div.rst-block  pre, code {
+font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
+}
+
+div.rst-block  code {
+    font-size: 12px !important;
+    background-color: ghostWhite !important;
+    color: #444 !important;
+    padding: 0 .2em !important;
+    border: 1px solid #dedede !important;
+}
+
+div.rst-block  pre code {
+    padding: 0 !important;
+    font-size: 12px !important;
+    background-color: #eee !important;
+    border: none !important;
+}
+
+div.rst-block  pre {
+    margin: 1em 0;
+    font-size: 12px;
+    background-color: #eee;
+    border: 1px solid #ddd;
+    padding: 5px;
+    color: #444;
+    overflow: auto;
+    -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
+    -webkit-border-radius: 3px;
+    -moz-border-radius: 3px;
+    border-radius: 3px;
+}
+
+
+/** comment main **/
+.comments {
+    padding:10px 20px;
+}
+
+.comments .comment {
+    border: 1px solid #ddd;
+    margin-top: 10px;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;    
+}
+
+.comments .comment .meta {
+    background: #f8f8f8;
+    padding: 4px;
+    border-bottom: 1px solid #ddd;
+}
+
+.comments .comment .meta img {
+    vertical-align: middle;
+}
+
+.comments .comment .meta .user {
+    font-weight: bold;
+}
+
+.comments .comment .meta .date {
+}
+
+.comments .comment .text {
+    background-color: #FAFAFA;
+}
+.comment .text div.rst-block p {
+	margin: 0.5em 0px !important;
+}
+
+.comments .comments-number{
+	padding:0px 0px 10px 0px;
+	font-weight: bold;
+	color: #666;
+	font-size: 16px;
+}
+
+/** comment form **/
+
+.comment-form .clearfix{
+	background: #EEE;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+    padding: 10px;
+}
+
+div.comment-form {
+    margin-top: 20px;
+}
+
+.comment-form strong {
+    display: block;
+    margin-bottom: 15px;
+}
+
+.comment-form textarea {
+    width: 100%;
+    height: 100px;
+    font-family: 'Monaco', 'Courier', 'Courier New', monospace;
+}
+
+form.comment-form {
+    margin-top: 10px;
+    margin-left: 10px;
+}
+
+.comment-form-submit {
+    margin-top: 5px;
+    margin-left: 525px;
+}
+
+.file-comments {
+    display: none;
+}
+
+.comment-form .comment {
+    margin-left: 10px;
+}
+
+.comment-form .comment-help{
+    padding: 0px 0px 5px 0px;
+    color: #666;
+}
+
+.comment-form .comment-button{
+	padding-top:5px;
+}
+
+.add-another-button {
+    margin-left: 10px;
+    margin-top: 10px;
+    margin-bottom: 10px;
+}
+
+.comment .buttons {
+	float: right;
+}
+
+
+.show-inline-comments{
+	position: relative;
+	top:1px
+}
+
+/** comment inline form **/
+
+.comment-inline-form .clearfix{
+    background: #EEE;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+    padding: 5px;
+}
+
+div.comment-inline-form {
+    margin-top: 5px;
+    padding:2px 6px 8px 6px;
+}
+
+.comment-inline-form strong {
+    display: block;
+    margin-bottom: 15px;
+}
+
+.comment-inline-form textarea {
+    width: 100%;
+    height: 100px;
+    font-family: 'Monaco', 'Courier', 'Courier New', monospace;
+}
+
+form.comment-inline-form {
+    margin-top: 10px;
+    margin-left: 10px;
+}
+
+.comment-inline-form-submit {
+    margin-top: 5px;
+    margin-left: 525px;
+}
+
+.file-comments {
+    display: none;
+}
+
+.comment-inline-form .comment {
+    margin-left: 10px;
+}
+
+.comment-inline-form .comment-help{
+    padding: 0px 0px 2px 0px;
+    color: #666666;
+    font-size: 10px;
+}
+
+.comment-inline-form .comment-button{
+    padding-top:5px;
+}
+
+/** comment inline **/
+.inline-comments {
+    padding:10px 20px;
+}
+
+.inline-comments div.rst-block  {
+	clear:both;
+	overflow:hidden;
+	margin:0;
+	padding:0 20px 0px;
+}
+.inline-comments .comment {
+    border: 1px solid #ddd;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+    margin: 3px 3px 5px 5px;
+    background-color: #FAFAFA;
+}
+.inline-comments .comment-wrapp{
+	padding:1px;
+}
+.inline-comments .comment .meta {
+    background: #f8f8f8;
+    padding: 4px;
+    border-bottom: 1px solid #ddd;
+}
+
+.inline-comments .comment .meta img {
+    vertical-align: middle;
+}
+
+.inline-comments .comment .meta .user {
+    font-weight: bold;
+}
+
+.inline-comments .comment .meta .date {
+}
+
+.inline-comments .comment .text {
+    background-color: #FAFAFA;
+}
+
+.inline-comments .comments-number{
+    padding:0px 0px 10px 0px;
+    font-weight: bold;
+    color: #666;
+    font-size: 16px;
+}
+.inline-comments-button .add-comment{
+	margin:10px 5px !important;
+}
+.notifications{
+    border-radius: 4px 4px 4px 4px;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;    
+    float: right;
+    margin: 20px 0px 0px 0px;
+    position: absolute;
+    text-align: center;
+    width: 26px;
+    z-index: 1000;
+}
+.notifications a{
+	color:#888 !important;
+	display: block;
+	font-size: 10px;
+	background-color: #DEDEDE !important;
+    border-radius: 2px !important;
+    -webkit-border-radius: 2px !important;
+    -moz-border-radius: 2px !important;  	
+}
+.notifications a:hover{
+	text-decoration: none !important;
+	background-color: #EEEFFF !important;
+}
+.notification-header{
+	padding-top:6px;
+}
+.notification-header .desc{
+	font-size: 16px;
+    height: 24px;
+    float: left
+}
+.notification-list .container.unread{
+	
+}
+.notification-header .gravatar{
+	
+}
+.notification-header .desc.unread{
+    font-weight: bold;
+    font-size: 17px;
+}
+
+.notification-header .delete-notifications{
+    float: right;
+    padding-top: 8px;
+    cursor: pointer;
+}
+.notification-subject{
+    clear:both;
+    border-bottom: 1px solid #eee;
+    padding:5px 0px 5px 38px;
+}
+
+
+/*****************************************************************************
+                                  DIFFS CSS
+******************************************************************************/
+
+div.diffblock {
+    overflow: auto;
+    padding: 0px;
+    border: 1px solid #ccc;
+    background: #f8f8f8;
+    font-size: 100%;
+    line-height: 100%;
+    /* new */
+    line-height: 125%;
+    -webkit-border-radius: 6px 6px 0px 0px;
+    -moz-border-radius: 6px 6px 0px 0px;
+    border-radius: 6px 6px 0px 0px;     
+}
+div.diffblock.margined{
+    margin: 0px 20px 0px 20px;
+}
+div.diffblock .code-header{
+    border-bottom: 1px solid #CCCCCC;
+    background: #EEEEEE;
+    padding:10px 0 10px 0;
+    height: 14px;
+}
+div.diffblock .code-header.cv{
+    height: 34px;
+}
+div.diffblock .code-header-title{
+	padding: 0px 0px 10px 5px !important;
+	margin: 0 !important;
+}
+div.diffblock .code-header .hash{
+    float: left;
+    padding: 2px 0 0 2px;
+}
+div.diffblock .code-header .date{
+    float:left;
+    text-transform: uppercase;
+    padding: 2px 0px 0px 2px;
+}
+div.diffblock .code-header div{
+    margin-left:4px;
+    font-weight: bold;
+    font-size: 14px;
+}
+div.diffblock .code-body{
+    background: #FFFFFF;
+}
+div.diffblock pre.raw{
+    background: #FFFFFF;
+    color:#000000;
+}
+table.code-difftable{
+    border-collapse: collapse;
+    width: 99%;
+}
+table.code-difftable td {
+    padding: 0 !important; 
+    background: none !important; 
+    border:0 !important;
+    vertical-align: none !important;
+}
+table.code-difftable .context{
+    background:none repeat scroll 0 0 #DDE7EF;
+}
+table.code-difftable .add{
+    background:none repeat scroll 0 0 #DDFFDD;
+}
+table.code-difftable .add ins{
+    background:none repeat scroll 0 0 #AAFFAA;
+    text-decoration:none;
+}
+table.code-difftable .del{
+    background:none repeat scroll 0 0 #FFDDDD;
+}
+table.code-difftable .del del{
+    background:none repeat scroll 0 0 #FFAAAA;
+    text-decoration:none;
+}
+
+/** LINE NUMBERS **/
+table.code-difftable .lineno{
+
+    padding-left:2px;
+    padding-right:2px;
+    text-align:right;
+    width:32px;
+    -moz-user-select:none;
+    -webkit-user-select: none;
+    border-right: 1px solid #CCC !important;
+    border-left: 0px solid #CCC !important;
+    border-top: 0px solid #CCC !important;
+    border-bottom: none !important;
+    vertical-align: middle !important;
+    
+}
+table.code-difftable .lineno.new {
+}
+table.code-difftable .lineno.old {
+}
+table.code-difftable .lineno a{
+    color:#747474 !important;
+    font:11px "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace !important;
+    letter-spacing:-1px;
+    text-align:right;
+    padding-right: 2px;
+    cursor: pointer;
+    display: block;
+    width: 32px;
+}
+
+table.code-difftable .lineno-inline{
+    background:none repeat scroll 0 0 #FFF !important;
+    padding-left:2px;
+    padding-right:2px;
+    text-align:right;
+    width:30px;
+    -moz-user-select:none;
+    -webkit-user-select: none;
+}
+
+/** CODE **/
+table.code-difftable .code { 
+    display: block;
+    width: 100%;
+}
+table.code-difftable .code td{
+    margin:0;
+    padding:0;
+}
+table.code-difftable .code pre{
+    margin:0;
+    padding:0;
+    height: 17px;
+    line-height: 17px;
+}
+
+
+.diffblock.margined.comm .line .code:hover{
+    background-color:#FFFFCC !important;
+    cursor: pointer !important;
+    background-image:url("../images/icons/comment_add.png") !important;
+    background-repeat:no-repeat !important;
+    background-position: right !important;
+    background-position: 0% 50% !important;
+}
+.diffblock.margined.comm .line .code.no-comment:hover{
+	background-image: none !important;
+	cursor: auto !important;
+	background-color: inherit !important;
+	
+}
Binary file rhodecode/public/images/button_highlight_selected.png has changed
Binary file rhodecode/public/images/dt-arrow-dn.png has changed
Binary file rhodecode/public/images/dt-arrow-up.png has changed
Binary file rhodecode/public/images/header_inner.png has changed
Binary file rhodecode/public/images/horizontal-indicator.png has changed
Binary file rhodecode/public/images/sprite.png has changed
Binary file rhodecode/public/images/title.png has changed
Binary file rhodecode/public/images/title_link.png has changed
--- a/rhodecode/public/js/codemirror.js	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/public/js/codemirror.js	Sun Feb 26 17:25:09 2012 +0200
@@ -13,32 +13,45 @@
       if (defaults.hasOwnProperty(opt))
         options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
 
-    // The element in which the editor lives. Takes care of scrolling
-    // (if enabled).
-    var wrapper = document.createElement("div");
+    var targetDocument = options["document"];
+    // The element in which the editor lives.
+    var wrapper = targetDocument.createElement("div");
     wrapper.className = "CodeMirror";
     // This mess creates the base DOM structure for the editor.
     wrapper.innerHTML =
-      '<div style="position: relative">' + // Set to the height of the text, causes scrolling
-        '<pre style="position: relative; height: 0; visibility: hidden; overflow: hidden;">' + // To measure line/char size
-           '<span>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</span></pre>' +
-        '<div style="position: relative">' + // Moved around its parent to cover visible view
-          '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
-          '<div style="overflow: hidden; position: absolute; width: 0; left: 0">' + // Wraps and hides input textarea
-            '<textarea style="height: 1px; position: absolute; width: 1px;" wrap="off"></textarea></div>' +
-          // Provides positioning relative to (visible) text origin
-          '<div class="CodeMirror-lines"><div style="position: relative">' +
-            '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
-            '<div></div></div></div></div></div>'; // This DIV contains the actual code
+      '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea
+        '<textarea style="position: absolute; width: 10000px;" wrap="off" ' +
+          'autocorrect="off" autocapitalize="off"></textarea></div>' +
+      '<div class="CodeMirror-scroll cm-s-' + options.theme + '">' +
+        '<div style="position: relative">' + // Set to the height of the text, causes scrolling
+          '<div style="position: absolute; height: 0; width: 0; overflow: hidden;"></div>' +
+          '<div style="position: relative">' + // Moved around its parent to cover visible view
+            '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
+            // Provides positioning relative to (visible) text origin
+            '<div class="CodeMirror-lines"><div style="position: relative" draggable="true">' +
+              '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
+              '<div></div>' + // This DIV contains the actual code
+            '</div></div></div></div></div>';
     if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
     // I've never seen more elegant code in my life.
-    var code = wrapper.firstChild, measure = code.firstChild, mover = measure.nextSibling,
+    var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
+        scroller = wrapper.lastChild, code = scroller.firstChild,
+        measure = code.firstChild, mover = measure.nextSibling,
         gutter = mover.firstChild, gutterText = gutter.firstChild,
-        inputDiv = gutter.nextSibling, input = inputDiv.firstChild,
-        lineSpace = inputDiv.nextSibling.firstChild, cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
+        lineSpace = gutter.nextSibling.firstChild,
+        cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
     if (options.tabindex != null) input.tabindex = options.tabindex;
     if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
 
+    // Check for problem with IE innerHTML not working when we have a
+    // P (or similar) parent node.
+    try { stringWidth("x"); }
+    catch (e) {
+      if (e.message.match(/unknown runtime/i))
+        e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
+      throw e;
+    }
+
     // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
     var poll = new Delayed(), highlight = new Delayed(), blinker;
 
@@ -46,7 +59,7 @@
     // (see Line constructor), work an array of lines that should be
     // parsed, and history the undo history (instance of History
     // constructor).
-    var mode, lines = [new Line("")], work, history = new History(), focused;
+    var mode, lines = [new Line("")], work, focused;
     loadMode();
     // The selection. These are always maintained to point at valid
     // positions. Inverted is used to remember that the user is
@@ -56,10 +69,10 @@
     // whether the user is holding shift. reducedSelection is a hack
     // to get around the fact that we can't create inverted
     // selections. See below.
-    var shiftSelecting, reducedSelection;
+    var shiftSelecting, reducedSelection, lastClick, lastDoubleClick, draggingText;
     // Variables used by startOperation/endOperation to track what
     // happened during the operation.
-    var updateInput, changes, textChanged, selectionChanged, leaveInputAlone;
+    var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty;
     // Current visible range (may be bigger than the view window).
     var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
     // editing will hold an object describing the things we put in the
@@ -67,35 +80,46 @@
     // bracketHighlighted is used to remember that a backet has been
     // marked.
     var editing, bracketHighlighted;
+    // Tracks the maximum line length so that the horizontal scrollbar
+    // can be kept static when scrolling.
+    var maxLine = "", maxWidth;
 
-    // Initialize the content. Somewhat hacky (delayed prepareInput)
-    // to work around browser issues.
+    // Initialize the content.
     operation(function(){setValue(options.value || ""); updateInput = false;})();
-    setTimeout(prepareInput, 20);
+    var history = new History();
 
     // Register our event handlers.
-    connect(wrapper, "mousedown", operation(onMouseDown));
+    connect(scroller, "mousedown", operation(onMouseDown));
+    connect(scroller, "dblclick", operation(onDoubleClick));
+    connect(lineSpace, "dragstart", onDragStart);
     // Gecko browsers fire contextmenu *after* opening the menu, at
     // which point we can't mess with it anymore. Context menu is
     // handled in onMouseDown for Gecko.
-    if (!gecko) connect(wrapper, "contextmenu", operation(onContextMenu));
-    connect(code, "dblclick", operation(onDblClick));
-    connect(wrapper, "scroll", function() {updateDisplay([]); if (options.onScroll) options.onScroll(instance);});
+    if (!gecko) connect(scroller, "contextmenu", onContextMenu);
+    connect(scroller, "scroll", function() {
+      updateDisplay([]);
+      if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
+      if (options.onScroll) options.onScroll(instance);
+    });
     connect(window, "resize", function() {updateDisplay(true);});
     connect(input, "keyup", operation(onKeyUp));
+    connect(input, "input", function() {fastPoll(curKeyId);});
     connect(input, "keydown", operation(onKeyDown));
     connect(input, "keypress", operation(onKeyPress));
     connect(input, "focus", onFocus);
     connect(input, "blur", onBlur);
 
-    connect(wrapper, "dragenter", function(e){e.stop();});
-    connect(wrapper, "dragover", function(e){e.stop();});
-    connect(wrapper, "drop", operation(onDrop));
-    connect(wrapper, "paste", function(){input.focus(); fastPoll();});
+    connect(scroller, "dragenter", e_stop);
+    connect(scroller, "dragover", e_stop);
+    connect(scroller, "drop", operation(onDrop));
+    connect(scroller, "paste", function(){focusInput(); fastPoll();});
     connect(input, "paste", function(){fastPoll();});
     connect(input, "cut", function(){fastPoll();});
 
-    if (document.activeElement == input) onFocus();
+    // IE throws unspecified error in certain cases, when
+    // trying to access activeElement before onload
+    var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }
+    if (hasFocus) setTimeout(onFocus, 20);
     else onBlur();
 
     function isLine(l) {return l >= 0 && l < lines.length;}
@@ -104,27 +128,37 @@
     // range checking and/or clipping. operation is used to wrap the
     // call so that changes it makes are tracked, and the display is
     // updated afterwards.
-    var instance = {
+    var instance = wrapper.CodeMirror = {
       getValue: getValue,
       setValue: operation(setValue),
       getSelection: getSelection,
       replaceSelection: operation(replaceSelection),
-      focus: function(){input.focus(); onFocus(); fastPoll();},
+      focus: function(){focusInput(); onFocus(); fastPoll();},
       setOption: function(option, value) {
         options[option] = value;
-        if (option == "lineNumbers" || option == "gutter") gutterChanged();
+        if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber")
+          operation(gutterChanged)();
         else if (option == "mode" || option == "indentUnit") loadMode();
+        else if (option == "readOnly" && value == "nocursor") input.blur();
+        else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value);
       },
       getOption: function(option) {return options[option];},
       undo: operation(undo),
       redo: operation(redo),
-      indentLine: operation(function(n) {if (isLine(n)) indentLine(n, "smart");}),
+      indentLine: operation(function(n, dir) {
+        if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract");
+      }),
       historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
+      clearHistory: function() {history = new History();},
       matchBrackets: operation(function(){matchBrackets(true);}),
       getTokenAt: function(pos) {
         pos = clipPos(pos);
         return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch);
       },
+      getStateAfter: function(line) {
+        line = clipLine(line == null ? lines.length - 1: line);
+        return getStateBefore(line + 1);
+      },
       cursorCoords: function(start){
         if (start == null) start = sel.inverted;
         return pageCoords(start ? sel.from : sel.to);
@@ -132,22 +166,41 @@
       charCoords: function(pos){return pageCoords(clipPos(pos));},
       coordsChar: function(coords) {
         var off = eltOffset(lineSpace);
-        var line = Math.min(showingTo - 1, showingFrom + Math.floor(coords.y / lineHeight()));
-        return clipPos({line: line, ch: charFromX(clipLine(line), coords.x)});
+        var line = clipLine(Math.min(lines.length - 1, showingFrom + Math.floor((coords.y - off.top) / lineHeight())));
+        return clipPos({line: line, ch: charFromX(clipLine(line), coords.x - off.left)});
       },
       getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);},
-      markText: operation(function(a, b, c){return operation(markText(a, b, c));}),
-      setMarker: addGutterMarker,
-      clearMarker: removeGutterMarker,
+      markText: operation(markText),
+      setMarker: operation(addGutterMarker),
+      clearMarker: operation(removeGutterMarker),
       setLineClass: operation(setLineClass),
       lineInfo: lineInfo,
-      addWidget: function(pos, node, scroll) {
-        var pos = localCoords(clipPos(pos), true);
-        node.style.top = (showingFrom * lineHeight() + pos.yBot + paddingTop()) + "px";
-        node.style.left = (pos.x + paddingLeft()) + "px";
+      addWidget: function(pos, node, scroll, vert, horiz) {
+        pos = localCoords(clipPos(pos));
+        var top = pos.yBot, left = pos.x;
+        node.style.position = "absolute";
         code.appendChild(node);
+        if (vert == "over") top = pos.y;
+        else if (vert == "near") {
+          var vspace = Math.max(scroller.offsetHeight, lines.length * lineHeight()),
+              hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
+          if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
+            top = pos.y - node.offsetHeight;
+          if (left + node.offsetWidth > hspace)
+            left = hspace - node.offsetWidth;
+        }
+        node.style.top = (top + paddingTop()) + "px";
+        node.style.left = node.style.right = "";
+        if (horiz == "right") {
+          left = code.clientWidth - node.offsetWidth;
+          node.style.right = "0px";
+        } else {
+          if (horiz == "left") left = 0;
+          else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2;
+          node.style.left = (left + paddingLeft()) + "px";
+        }
         if (scroll)
-          scrollIntoView(pos.x, pos.yBot, pos.x + node.offsetWidth, pos.yBot + node.offsetHeight);
+          scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
       },
 
       lineCount: function() {return lines.length;},
@@ -171,18 +224,30 @@
       replaceRange: operation(replaceRange),
       getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
 
+      coordsFromIndex: function(index) {        
+        var total = lines.length, pos = 0, line, ch, len;
+        
+        for (line = 0; line < total; line++) {
+          len = lines[line].text.length + 1;
+          if (pos + len > index) { ch = index - pos; break; }
+          pos += len;
+        }
+        return clipPos({line: line, ch: ch});
+      },
+
       operation: function(f){return operation(f)();},
       refresh: function(){updateDisplay(true);},
       getInputField: function(){return input;},
-      getWrapperElement: function(){return wrapper;}
+      getWrapperElement: function(){return wrapper;},
+      getScrollerElement: function(){return scroller;},
+      getGutterElement: function(){return gutter;}
     };
 
     function setValue(code) {
-      history = null;
       var top = {line: 0, ch: 0};
       updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length},
                   splitLines(code), top, top);
-      history = new History();
+      updateInput = true;
     }
     function getValue(code) {
       var text = [];
@@ -192,38 +257,70 @@
     }
 
     function onMouseDown(e) {
+      // Check whether this is a click in a widget
+      for (var n = e_target(e); n != wrapper; n = n.parentNode)
+        if (n.parentNode == code && n != mover) return;
+
       // First, see if this is a click in the gutter
-      for (var n = e.target(); n != wrapper; n = n.parentNode)
+      for (var n = e_target(e); n != wrapper; n = n.parentNode)
         if (n.parentNode == gutterText) {
           if (options.onGutterClick)
-            options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom);
-          return e.stop();
+            options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
+          return e_preventDefault(e);
         }
 
-      if (gecko && e.button() == 3) onContextMenu(e);
-      if (e.button() != 1) return;
+      var start = posFromMouse(e);
+
+      switch (e_button(e)) {
+      case 3:
+        if (gecko && !mac) onContextMenu(e);
+        return;
+      case 2:
+        if (start) setCursor(start.line, start.ch, true);
+        return;
+      }
       // For button 1, if it was clicked inside the editor
       // (posFromMouse returning non-null), we have to adjust the
       // selection.
-      var start = posFromMouse(e), last = start, going;
-      if (!start) {if (e.target() == wrapper) e.stop(); return;}
-      setCursor(start.line, start.ch, false);
+      if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
 
       if (!focused) onFocus();
-      e.stop();
-      // And then we have to see if it's a drag event, in which case
-      // the dragged-over text must be selected.
-      function end() {
-        input.focus();
-        updateInput = true;
-        move(); up();
+
+      var now = +new Date;
+      if (lastDoubleClick > now - 400) {
+        e_preventDefault(e);
+        return selectLine(start.line);
+      } else if (lastClick > now - 400) {
+        lastDoubleClick = now;
+        e_preventDefault(e);
+        return selectWordAt(start);
+      } else { lastClick = now; }
+
+      var last = start, going;
+      if (dragAndDrop && !posEq(sel.from, sel.to) &&
+          !posLess(start, sel.from) && !posLess(sel.to, start)) {
+        // Let the drag handler handle this.
+        var up = connect(targetDocument, "mouseup", operation(function(e2) {
+          draggingText = false;
+          up();
+          if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
+            e_preventDefault(e2);
+            setCursor(start.line, start.ch, true);
+            focusInput();
+          }
+        }), true);
+        draggingText = true;
+        return;
       }
+      e_preventDefault(e);
+      setCursor(start.line, start.ch, true);
+
       function extend(e) {
         var cur = posFromMouse(e, true);
         if (cur && !posEq(cur, last)) {
           if (!focused) onFocus();
           last = cur;
-          setSelection(start, cur);
+          setSelectionUser(start, cur);
           updateInput = false;
           var visible = visibleLines();
           if (cur.line >= visible.to || cur.line < visible.from)
@@ -231,68 +328,95 @@
         }
       }
 
-      var move = connect(document, "mousemove", operation(function(e) {
+      var move = connect(targetDocument, "mousemove", operation(function(e) {
         clearTimeout(going);
-        e.stop();
+        e_preventDefault(e);
         extend(e);
       }), true);
-      var up = connect(document, "mouseup", operation(function(e) {
+      var up = connect(targetDocument, "mouseup", operation(function(e) {
         clearTimeout(going);
         var cur = posFromMouse(e);
-        if (cur) setSelection(start, cur);
-        e.stop();
-        end();
+        if (cur) setSelectionUser(start, cur);
+        e_preventDefault(e);
+        focusInput();
+        updateInput = true;
+        move(); up();
       }), true);
     }
-    function onDblClick(e) {
-      var pos = posFromMouse(e);
-      if (!pos) return;
-      selectWordAt(pos);
-      e.stop();
+    function onDoubleClick(e) {
+      var start = posFromMouse(e);
+      if (!start) return;
+      lastDoubleClick = +new Date;
+      e_preventDefault(e);
+      selectWordAt(start);
     }
     function onDrop(e) {
-      var pos = posFromMouse(e, true), files = e.e.dataTransfer.files;
+      e.preventDefault();
+      var pos = posFromMouse(e, true), files = e.dataTransfer.files;
       if (!pos || options.readOnly) return;
       if (files && files.length && window.FileReader && window.File) {
-        var n = files.length, text = Array(n), read = 0;
-        for (var i = 0; i < n; ++i) loadFile(files[i], i);
         function loadFile(file, i) {
           var reader = new FileReader;
           reader.onload = function() {
             text[i] = reader.result;
-            if (++read == n) replaceRange(text.join(""), clipPos(pos), clipPos(pos));
+            if (++read == n) {
+	      pos = clipPos(pos);
+	      var end = replaceRange(text.join(""), pos, pos);
+	      setSelectionUser(pos, end);
+	    }
           };
           reader.readAsText(file);
         }
+        var n = files.length, text = Array(n), read = 0;
+        for (var i = 0; i < n; ++i) loadFile(files[i], i);
       }
       else {
         try {
-          var text = e.e.dataTransfer.getData("Text");
-          if (text) replaceRange(text, pos, pos);
+          var text = e.dataTransfer.getData("Text");
+          if (text) {
+	    var end = replaceRange(text, pos, pos);
+	    var curFrom = sel.from, curTo = sel.to;
+	    setSelectionUser(pos, end);
+            if (draggingText) replaceRange("", curFrom, curTo);
+	    focusInput();
+	  }
         }
         catch(e){}
       }
     }
+    function onDragStart(e) {
+      var txt = getSelection();
+      // This will reset escapeElement
+      htmlEscape(txt);
+      e.dataTransfer.setDragImage(escapeElement, 0, 0);
+      e.dataTransfer.setData("Text", txt);
+    }
     function onKeyDown(e) {
       if (!focused) onFocus();
 
-      var code = e.e.keyCode;
+      var code = e.keyCode;
+      // IE does strange things with escape.
+      if (ie && code == 27) { e.returnValue = false; }
       // Tries to detect ctrl on non-mac, cmd on mac.
-      var mod = (mac ? e.e.metaKey : e.e.ctrlKey) && !e.e.altKey, anyMod = e.e.ctrlKey || e.e.altKey || e.e.metaKey;
-      if (code == 16 || e.e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
+      var mod = (mac ? e.metaKey : e.ctrlKey) && !e.altKey, anyMod = e.ctrlKey || e.altKey || e.metaKey;
+      if (code == 16 || e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
       else shiftSelecting = null;
       // First give onKeyEvent option a chance to handle this.
-      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e.e))) return;
+      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
 
-      if (code == 33 || code == 34) {scrollPage(code == 34); return e.stop();} // page up/down
-      if (mod && (code == 36 || code == 35)) {scrollEnd(code == 36); return e.stop();} // ctrl-home/end
-      if (mod && code == 65) {selectAll(); return e.stop();} // ctrl-a
+      if (code == 33 || code == 34) {scrollPage(code == 34); return e_preventDefault(e);} // page up/down
+      if (mod && ((code == 36 || code == 35) || // ctrl-home/end
+                  mac && (code == 38 || code == 40))) { // cmd-up/down
+        scrollEnd(code == 36 || code == 38); return e_preventDefault(e);
+      }
+      if (mod && code == 65) {selectAll(); return e_preventDefault(e);} // ctrl-a
       if (!options.readOnly) {
         if (!anyMod && code == 13) {return;} // enter
-        if (!anyMod && code == 9 && handleTab(e.e.shiftKey)) return e.stop(); // tab
-        if (mod && code == 90) {undo(); return e.stop();} // ctrl-z
-        if (mod && ((e.e.shiftKey && code == 90) || code == 89)) {redo(); return e.stop();} // ctrl-shift-z, ctrl-y
+        if (!anyMod && code == 9 && handleTab(e.shiftKey)) return e_preventDefault(e); // tab
+        if (mod && code == 90) {undo(); return e_preventDefault(e);} // ctrl-z
+        if (mod && ((e.shiftKey && code == 90) || code == 89)) {redo(); return e_preventDefault(e);} // ctrl-shift-z, ctrl-y
       }
+      if (code == 36) { if (options.smartHome) { smartHome(); return e_preventDefault(e); } }
 
       // Key id to use in the movementKeys map. We also pass it to
       // fastPoll in order to 'self learn'. We need this because
@@ -300,51 +424,60 @@
       // its start when it is inverted and a movement key is pressed
       // (and later restore it again), shouldn't be used for
       // non-movement keys.
-      curKeyId = (mod ? "c" : "") + code;
-      if (sel.inverted && movementKeys.hasOwnProperty(curKeyId)) {
+      curKeyId = (mod ? "c" : "") + (e.altKey ? "a" : "") + code;
+      if (sel.inverted && movementKeys[curKeyId] === true) {
         var range = selRange(input);
         if (range) {
           reducedSelection = {anchor: range.start};
           setSelRange(input, range.start, range.start);
         }
       }
+      // Don't save the key as a movementkey unless it had a modifier
+      if (!mod && !e.altKey) curKeyId = null;
       fastPoll(curKeyId);
     }
     function onKeyUp(e) {
+      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
       if (reducedSelection) {
         reducedSelection = null;
         updateInput = true;
       }
-      if (e.e.keyCode == 16) shiftSelecting = null;
+      if (e.keyCode == 16) shiftSelecting = null;
     }
     function onKeyPress(e) {
-      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e.e))) return;
+      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
       if (options.electricChars && mode.electricChars) {
-        var ch = String.fromCharCode(e.e.charCode == null ? e.e.keyCode : e.e.charCode);
+        var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode);
         if (mode.electricChars.indexOf(ch) > -1)
           setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50);
       }
-      var code = e.e.keyCode;
+      var code = e.keyCode;
       // Re-stop tab and enter. Necessary on some browsers.
-      if (code == 13) {handleEnter(); e.stop();}
-      else if (code == 9 && options.tabMode != "default") e.stop();
+      if (code == 13) {if (!options.readOnly) handleEnter(); e_preventDefault(e);}
+      else if (!e.ctrlKey && !e.altKey && !e.metaKey && code == 9 && options.tabMode != "default") e_preventDefault(e);
       else fastPoll(curKeyId);
     }
 
     function onFocus() {
-      if (!focused && options.onFocus) options.onFocus(instance);
-      focused = true;
+      if (options.readOnly == "nocursor") return;
+      if (!focused) {
+        if (options.onFocus) options.onFocus(instance);
+        focused = true;
+        if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
+          wrapper.className += " CodeMirror-focused";
+        if (!leaveInputAlone) prepareInput();
+      }
       slowPoll();
-      if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
-        wrapper.className += " CodeMirror-focused";
       restartBlink();
     }
     function onBlur() {
-      if (focused && options.onBlur) options.onBlur(instance);
+      if (focused) {
+        if (options.onBlur) options.onBlur(instance);
+        focused = false;
+        wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
+      }
       clearInterval(blinker);
-      shiftSelecting = null;
-      focused = false;
-      wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
+      setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
     }
 
     // Replace the range from from to to by the strings in newText.
@@ -367,12 +500,18 @@
         var pos = clipPos({line: change.start + change.old.length - 1,
                            ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
         updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos);
+        updateInput = true;
       }
     }
     function undo() {unredoHelper(history.done, history.undone);}
     function redo() {unredoHelper(history.undone, history.done);}
 
     function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
+      var recomputeMaxLength = false, maxLineLength = maxLine.length;
+      for (var i = from.line; i <= to.line; ++i) {
+        if (lines[i].text.length == maxLineLength) {recomputeMaxLength = true; break;}
+      }
+
       var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line];
       // First adjust the line structure, taking some care to leave highlighting intact.
       if (firstLine == lastLine) {
@@ -381,24 +520,46 @@
         else {
           lastLine = firstLine.split(to.ch, newText[newText.length-1]);
           var spliceargs = [from.line + 1, nlines];
-          firstLine.replace(from.ch, firstLine.text.length, newText[0]);
-          for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
+          firstLine.replace(from.ch, null, newText[0]);
+          for (var i = 1, e = newText.length - 1; i < e; ++i)
+            spliceargs.push(Line.inheritMarks(newText[i], firstLine));
           spliceargs.push(lastLine);
           lines.splice.apply(lines, spliceargs);
         }
       }
       else if (newText.length == 1) {
-        firstLine.replace(from.ch, firstLine.text.length, newText[0] + lastLine.text.slice(to.ch));
+        firstLine.replace(from.ch, null, newText[0]);
+        lastLine.replace(null, to.ch, "");
+        firstLine.append(lastLine);
         lines.splice(from.line + 1, nlines);
       }
       else {
         var spliceargs = [from.line + 1, nlines - 1];
-        firstLine.replace(from.ch, firstLine.text.length, newText[0]);
-        lastLine.replace(0, to.ch, newText[newText.length-1]);
-        for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
+        firstLine.replace(from.ch, null, newText[0]);
+        lastLine.replace(null, to.ch, newText[newText.length-1]);
+        for (var i = 1, e = newText.length - 1; i < e; ++i)
+          spliceargs.push(Line.inheritMarks(newText[i], firstLine));
         lines.splice.apply(lines, spliceargs);
       }
 
+
+      for (var i = from.line, e = i + newText.length; i < e; ++i) {
+        var l = lines[i].text;
+        if (l.length > maxLineLength) {
+          maxLine = l; maxLineLength = l.length; maxWidth = null;
+          recomputeMaxLength = false;
+        }
+      }
+      if (recomputeMaxLength) {
+        maxLineLength = 0; maxLine = ""; maxWidth = null;
+        for (var i = 0, e = lines.length; i < e; ++i) {
+          var l = lines[i].text;
+          if (l.length > maxLineLength) {
+            maxLineLength = l.length; maxLine = l;
+          }
+        }
+      }
+
       // Add these lines to the work array, so that they will be
       // highlighted. Adjust work lines if lines were added/removed.
       var newWork = [], lendiff = newText.length - nlines - 1;
@@ -407,12 +568,17 @@
         if (task < from.line) newWork.push(task);
         else if (task > to.line) newWork.push(task + lendiff);
       }
-      if (newText.length) newWork.push(from.line);
+      if (newText.length < 5) {
+        highlightLines(from.line, from.line + newText.length);
+        newWork.push(from.line + newText.length);
+      } else {
+        newWork.push(from.line);
+      }
       work = newWork;
       startWorker(100);
       // Remember that these lines changed, for updating the display
       changes.push({from: from.line, to: to.line + 1, diff: lendiff});
-      textChanged = true;
+      textChanged = {from: from, to: to, text: newText};
 
       // Update the selection
       function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
@@ -483,7 +649,10 @@
       function p() {
         startOperation();
         var changed = readInput();
-        if (changed == "moved" && keyId) movementKeys[keyId] = true;
+        if (changed && keyId) {
+          if (changed == "moved" && movementKeys[keyId] == null) movementKeys[keyId] = true;
+          if (changed == "changed") movementKeys[keyId] = false;
+        }
         if (!changed && !missed) {missed = true; poll.set(80, p);}
         else {pollingFast = false; slowPoll();}
         endOperation();
@@ -495,13 +664,12 @@
     // to the data in the editing variable, and updates the editor
     // content or cursor if something changed.
     function readInput() {
+      if (leaveInputAlone || !focused) return;
       var changed = false, text = input.value, sr = selRange(input);
       if (!sr) return false;
       var changed = editing.text != text, rs = reducedSelection;
       var moved = changed || sr.start != editing.start || sr.end != (rs ? editing.start : editing.end);
-      if (reducedSelection && !moved && sel.from.line == 0 && sel.from.ch == 0)
-        reducedSelection = null;
-      else if (!moved) return false;
+      if (!moved && !rs) return false;
       if (changed) {
         shiftSelecting = reducedSelection = null;
         if (options.readOnly) {updateInput = true; return "changed";}
@@ -524,13 +692,10 @@
       // so that you can, for example, press shift-up at the start of
       // your selection and have the right thing happen.
       if (rs) {
-        from = sr.start == rs.anchor ? to : from;
-        to = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to;
-        if (!posLess(from, to)) {
-          reducedSelection = null;
-          sel.inverted = false;
-          var tmp = from; from = to; to = tmp;
-        }
+        var head = sr.start == rs.anchor ? to : from;
+        var tail = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to;
+        if (sel.inverted = posLess(head, tail)) { from = head; to = tail; }
+        else { reducedSelection = null; from = tail; to = head; }
       }
 
       // In some cases (cursor on same line as before), we don't have
@@ -550,8 +715,8 @@
         var ch = nl > -1 ? start - nl : start, endline = editing.to - 1, edend = editing.text.length;
         for (;;) {
           c = editing.text.charAt(edend);
+          if (text.charAt(end) != c) {++end; ++edend; break;}
           if (c == "\n") endline--;
-          if (text.charAt(end) != c) {++end; ++edend; break;}
           if (edend <= start || end <= start) break;
           --end; --edend;
         }
@@ -580,22 +745,36 @@
       editing = {text: text, from: from, to: to, start: startch, end: endch};
       setSelRange(input, startch, reducedSelection ? startch : endch);
     }
+    function focusInput() {
+      if (options.readOnly != "nocursor") input.focus();
+    }
 
+    function scrollEditorIntoView() {
+      if (!cursor.getBoundingClientRect) return;
+      var rect = cursor.getBoundingClientRect();
+      var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
+      if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();
+    }
     function scrollCursorIntoView() {
       var cursor = localCoords(sel.inverted ? sel.from : sel.to);
       return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot);
     }
     function scrollIntoView(x1, y1, x2, y2) {
-      var pl = paddingLeft(), pt = paddingTop();
+      var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight();
       y1 += pt; y2 += pt; x1 += pl; x2 += pl;
-      var screen = wrapper.clientHeight, screentop = wrapper.scrollTop, scrolled = false, result = true;
-      if (y1 < screentop) {wrapper.scrollTop = Math.max(0, y1 - 10); scrolled = true;}
-      else if (y2 > screentop + screen) {wrapper.scrollTop = y2 + 10 - screen; scrolled = true;}
+      var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
+      if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}
+      else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;}
 
-      var screenw = wrapper.clientWidth, screenleft = wrapper.scrollLeft;
-      if (x1 < screenleft) {wrapper.scrollLeft = Math.max(0, x1 - 10); scrolled = true;}
+      var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
+      var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
+      if (x1 < screenleft + gutterw) {
+        if (x1 < 50) x1 = 0;
+        scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
+        scrolled = true;
+      }
       else if (x2 > screenw + screenleft) {
-        wrapper.scrollLeft = x2 + 10 - screenw;
+        scroller.scrollLeft = x2 + 10 - screenw;
         scrolled = true;
         if (x2 > code.clientWidth) result = false;
       }
@@ -604,15 +783,15 @@
     }
 
     function visibleLines() {
-      var lh = lineHeight(), top = wrapper.scrollTop - paddingTop();
+      var lh = lineHeight(), top = scroller.scrollTop - paddingTop();
       return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))),
-              to: Math.min(lines.length, Math.ceil((top + wrapper.clientHeight) / lh))};
+              to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))};
     }
     // Uses a set of changes plus the current scroll position to
     // determine which DOM updates have to be made, and makes the
     // updates.
     function updateDisplay(changes) {
-      if (!wrapper.clientWidth) {
+      if (!scroller.clientWidth) {
         showingFrom = showingTo = 0;
         return;
       }
@@ -629,7 +808,7 @@
             intact2.push(range);
           else {
             if (change.from > range.from)
-              intact2.push({from: range.from, to: change.from, domStart: range.domStart})
+              intact2.push({from: range.from, to: change.from, domStart: range.domStart});
             if (change.to < range.to)
               intact2.push({from: change.to + diff, to: range.to + diff,
                             domStart: range.domStart + (change.to - range.from)});
@@ -659,6 +838,7 @@
       if (domPos != domEnd || pos != to) {
         changedLines += Math.abs(to - pos);
         updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos});
+        if (to - pos != domEnd - domPos) gutterDirty = true;
       }
 
       if (!updates.length) return;
@@ -674,13 +854,23 @@
 
       // Position the mover div to align with the lines it's supposed
       // to be showing (which will cover the visible display)
-      var different = from != showingFrom || to != showingTo || lastHeight != wrapper.clientHeight;
+      var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
       showingFrom = from; showingTo = to;
       mover.style.top = (from * lineHeight()) + "px";
       if (different) {
-        lastHeight = wrapper.clientHeight;
+        lastHeight = scroller.clientHeight;
         code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
-        updateGutter();
+      }
+      if (different || gutterDirty) updateGutter();
+
+      if (maxWidth == null) maxWidth = stringWidth(maxLine);
+      if (maxWidth > scroller.clientWidth) {
+        lineSpace.style.width = maxWidth + "px";
+        // Needed to prevent odd wrapping/hiding of widgets placed in here.
+        code.style.width = "";
+        code.style.width = scroller.scrollWidth + "px";
+      } else {
+        lineSpace.style.width = code.style.width = "";
       }
 
       // Since this is all rather error prone, it is honoured with the
@@ -712,7 +902,7 @@
       // there .innerHTML on PRE nodes is dumb, and discards
       // whitespace.
       var sfrom = sel.from.line, sto = sel.to.line, off = 0,
-          scratch = badInnerHTML && document.createElement("div");
+          scratch = badInnerHTML && targetDocument.createElement("div");
       for (var i = 0, e = updates.length; i < e; ++i) {
         var rec = updates[i];
         var extra = (rec.to - rec.from) - rec.domSize;
@@ -722,7 +912,7 @@
             lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
         else if (extra) {
           for (var j = Math.max(0, extra); j > 0; --j)
-            lineDiv.insertBefore(document.createElement("pre"), nodeAfter);
+            lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter);
           for (var j = Math.max(0, -extra); j > 0; --j)
             lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
         }
@@ -753,10 +943,10 @@
 
     function updateGutter() {
       if (!options.gutter && !options.lineNumbers) return;
-      var hText = mover.offsetHeight, hEditor = wrapper.clientHeight;
+      var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
       gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
       var html = [];
-      for (var i = showingFrom; i < showingTo; ++i) {
+      for (var i = showingFrom; i < Math.max(showingTo, showingFrom + 1); ++i) {
         var marker = lines[i].gutterMarker;
         var text = options.lineNumbers ? i + options.firstLineNumber : null;
         if (marker && marker.text)
@@ -769,37 +959,43 @@
       gutterText.innerHTML = html.join("");
       var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
       while (val.length + pad.length < minwidth) pad += "\u00a0";
-      if (pad) firstNode.insertBefore(document.createTextNode(pad), firstNode.firstChild);
+      if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);
       gutter.style.display = "";
       lineSpace.style.marginLeft = gutter.offsetWidth + "px";
+      gutterDirty = false;
     }
     function updateCursor() {
-      var head = sel.inverted ? sel.from : sel.to;
-      var x = charX(head.line, head.ch) + "px", y = (head.line - showingFrom) * lineHeight() + "px";
-      inputDiv.style.top = y; inputDiv.style.left = x;
+      var head = sel.inverted ? sel.from : sel.to, lh = lineHeight();
+      var x = charX(head.line, head.ch);
+      var top = head.line * lh - scroller.scrollTop;
+      inputDiv.style.top = Math.max(Math.min(top, scroller.offsetHeight), 0) + "px";
+      inputDiv.style.left = (x - scroller.scrollLeft) + "px";
       if (posEq(sel.from, sel.to)) {
-        cursor.style.top = y; cursor.style.left = x;
+        cursor.style.top = (head.line - showingFrom) * lh + "px";
+        cursor.style.left = x + "px";
         cursor.style.display = "";
       }
       else cursor.style.display = "none";
     }
 
+    function setSelectionUser(from, to) {
+      var sh = shiftSelecting && clipPos(shiftSelecting);
+      if (sh) {
+        if (posLess(sh, from)) from = sh;
+        else if (posLess(to, sh)) to = sh;
+      }
+      setSelection(from, to);
+    }
     // Update the selection. Last two args are only used by
     // updateLines, since they have to be expressed in the line
     // numbers before the update.
     function setSelection(from, to, oldFrom, oldTo) {
       if (posEq(sel.from, from) && posEq(sel.to, to)) return;
-      var sh = shiftSelecting && clipPos(shiftSelecting);
       if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
-      if (sh) {
-        if (posLess(sh, from)) from = sh;
-        else if (posLess(to, sh)) to = sh;
-      }
 
-      var startEq = posEq(sel.to, to), endEq = posEq(sel.from, from);
       if (posEq(from, to)) sel.inverted = false;
-      else if (startEq && !endEq) sel.inverted = true;
-      else if (endEq && !startEq) sel.inverted = false;
+      else if (posEq(from, sel.to)) sel.inverted = false;
+      else if (posEq(to, sel.from)) sel.inverted = true;
 
       // Some ugly logic used to only mark the lines that actually did
       // see a change in selection as changed, rather than the whole
@@ -829,9 +1025,9 @@
       sel.from = from; sel.to = to;
       selectionChanged = true;
     }
-    function setCursor(line, ch) {
+    function setCursor(line, ch, user) {
       var pos = clipPos({line: line, ch: ch || 0});
-      setSelection(pos, pos);
+      (user ? setSelectionUser : setSelection)(pos, pos);
     }
 
     function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));}
@@ -845,11 +1041,12 @@
     }
 
     function scrollPage(down) {
-      var linesPerPage = Math.floor(wrapper.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
-      setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch);
+      var linesPerPage = Math.floor(scroller.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
+      setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch, true);
     }
     function scrollEnd(top) {
-      setCursor(top ? 0 : lines.length - 1);
+      var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length};
+      setSelectionUser(pos, pos);
     }
     function selectAll() {
       var endLine = lines.length - 1;
@@ -859,8 +1056,11 @@
       var line = lines[pos.line].text;
       var start = pos.ch, end = pos.ch;
       while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
-      while (end < line.length - 1 && /\w/.test(line.charAt(end))) ++end;
-      setSelection({line: pos.line, ch: start}, {line: pos.line, ch: end});
+      while (end < line.length && /\w/.test(line.charAt(end))) ++end;
+      setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
+    }
+    function selectLine(line) {
+      setSelectionUser({line: line, ch: 0}, {line: line, ch: lines[line].text.length});
     }
     function handleEnter() {
       replaceSelection("\n", "end");
@@ -868,12 +1068,17 @@
         indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart");
     }
     function handleTab(shift) {
+      function indentSelected(mode) {
+        if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
+        var e = sel.to.line - (sel.to.ch ? 0 : 1);
+        for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
+      }
       shiftSelecting = null;
       switch (options.tabMode) {
       case "default":
         return false;
       case "indent":
-        for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, "smart");
+        indentSelected("smart");
         break;
       case "classic":
         if (posEq(sel.from, sel.to)) {
@@ -882,11 +1087,15 @@
           break;
         }
       case "shift":
-        for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, shift ? "subtract" : "add");
+        indentSelected(shift ? "subtract" : "add");
         break;
       }
       return true;
     }
+    function smartHome() {
+      var firstNonWS = Math.max(0, lines[sel.from.line].text.search(/\S/));
+      setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true);
+    }
 
     function indentLine(n, how) {
       if (how == "smart") {
@@ -924,21 +1133,20 @@
       for (var i = 0, l = lines.length; i < l; ++i)
         lines[i].stateAfter = null;
       work = [0];
+      startWorker();
     }
     function gutterChanged() {
       var visible = options.gutter || options.lineNumbers;
       gutter.style.display = visible ? "" : "none";
-      if (visible) updateGutter();
+      if (visible) gutterDirty = true;
       else lineDiv.parentNode.style.marginLeft = 0;
     }
 
     function markText(from, to, className) {
       from = clipPos(from); to = clipPos(to);
-      var accum = [];
+      var set = [];
       function add(line, from, to, className) {
-        var line = lines[line], mark = line.addMark(from, to, className);
-        mark.line = line;
-        accum.push(mark);
+        mark = lines[line].addMark(from, to, className, set);
       }
       if (from.line == to.line) add(from.line, from.ch, to.ch, className);
       else {
@@ -948,30 +1156,51 @@
         add(to.line, 0, to.ch, className);
       }
       changes.push({from: from.line, to: to.line + 1});
-      return function() {
-        var start, end;
-        for (var i = 0; i < accum.length; ++i) {
-          var mark = accum[i], found = indexOf(lines, mark.line);
-          mark.line.removeMark(mark);
-          if (found > -1) {
-            if (start == null) start = found;
-            end = found;
+      return new TextMarker(set);
+    }
+
+    function TextMarker(set) { this.set = set; }
+    TextMarker.prototype.clear = operation(function() {
+      for (var i = 0, e = this.set.length; i < e; ++i) {
+        var mk = this.set[i].marked;
+        for (var j = 0; j < mk.length; ++j) {
+          if (mk[j].set == this.set) mk.splice(j--, 1);
+        }
+      }
+      // We don't know the exact lines that changed. Refreshing is
+      // cheaper than finding them.
+      changes.push({from: 0, to: lines.length});
+    });
+    TextMarker.prototype.find = function() {
+      var from, to;
+      for (var i = 0, e = this.set.length; i < e; ++i) {
+        var line = this.set[i], mk = line.marked;
+        for (var j = 0; j < mk.length; ++j) {
+          var mark = mk[j];
+          if (mark.set == this.set) {
+            if (mark.from != null || mark.to != null) {
+              var found = indexOf(lines, line);
+              if (found > -1) {
+                if (mark.from != null) from = {line: found, ch: mark.from};
+                if (mark.to != null) to = {line: found, ch: mark.to};
+              }
+            }
           }
         }
-        if (start != null) changes.push({from: start, to: end + 1});
-      };
-    }
+      }
+      return {from: from, to: to};
+    };
 
     function addGutterMarker(line, text, className) {
       if (typeof line == "number") line = lines[clipLine(line)];
       line.gutterMarker = {text: text, style: className};
-      updateGutter();
+      gutterDirty = true;
       return line;
     }
     function removeGutterMarker(line) {
       if (typeof line == "number") line = lines[clipLine(line)];
       line.gutterMarker = null;
-      updateGutter();
+      gutterDirty = true;
     }
     function setLineClass(line, className) {
       if (typeof line == "number") {
@@ -982,8 +1211,10 @@
         var no = indexOf(lines, line);
         if (no == -1) return null;
       }
-      line.className = className;
-      changes.push({from: no, to: no + 1});
+      if (line.className != className) {
+        line.className = className;
+        changes.push({from: no, to: no + 1});
+      }
       return line;
     }
 
@@ -1001,35 +1232,44 @@
       return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style};
     }
 
+    function stringWidth(str) {
+      measure.innerHTML = "<pre><span>x</span></pre>";
+      measure.firstChild.firstChild.firstChild.nodeValue = str;
+      return measure.firstChild.firstChild.offsetWidth || 10;
+    }
     // These are used to go from pixel positions to character
-    // positions, taking tabs into account.
+    // positions, taking varying character widths into account.
     function charX(line, pos) {
-      var text = lines[line].text, span = measure.firstChild;
-      if (text.lastIndexOf("\t", pos) == -1) return pos * charWidth();
-      var old = span.firstChild.nodeValue;
-      try {
-        span.firstChild.nodeValue = text.slice(0, pos);
-        return span.offsetWidth;
-      } finally {span.firstChild.nodeValue = old;}
+      if (pos == 0) return 0;
+      measure.innerHTML = "<pre><span>" + lines[line].getHTML(null, null, false, pos) + "</span></pre>";
+      return measure.firstChild.firstChild.offsetWidth;
     }
     function charFromX(line, x) {
-      var text = lines[line].text, cw = charWidth();
       if (x <= 0) return 0;
-      if (text.indexOf("\t") == -1) return Math.min(text.length, Math.round(x / cw));
-      var mspan = measure.firstChild, mtext = mspan.firstChild, old = mtext.nodeValue;
-      try {
-        mtext.nodeValue = text;
-        var from = 0, fromX = 0, to = text.length, toX = mspan.offsetWidth;
-        if (x > toX) return to;
-        for (;;) {
-          if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
-          var middle = Math.ceil((from + to) / 2);
-          mtext.nodeValue = text.slice(0, middle);
-          var curX = mspan.offsetWidth;
-          if (curX > x) {to = middle; toX = curX;}
-          else {from = middle; fromX = curX;}
-        }
-      } finally {mtext.nodeValue = old;}
+      var lineObj = lines[line], text = lineObj.text;
+      function getX(len) {
+        measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>";
+        return measure.firstChild.firstChild.offsetWidth;
+      }
+      var from = 0, fromX = 0, to = text.length, toX;
+      // Guess a suitable upper bound for our search.
+      var estimated = Math.min(to, Math.ceil(x / stringWidth("x")));
+      for (;;) {
+        var estX = getX(estimated);
+        if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
+        else {toX = estX; to = estimated; break;}
+      }
+      if (x > toX) return to;
+      // Try to guess a suitable lower bound as well.
+      estimated = Math.floor(to * 0.8); estX = getX(estimated);
+      if (estX < x) {from = estimated; fromX = estX;}
+      // Do a binary search between these bounds.
+      for (;;) {
+        if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
+        var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
+        if (middleX > x) {to = middle; toX = middleX;}
+        else {from = middle; fromX = middleX;}
+      }
     }
 
     function localCoords(pos, inLineWrap) {
@@ -1043,45 +1283,61 @@
 
     function lineHeight() {
       var nlines = lineDiv.childNodes.length;
-      if (nlines) return lineDiv.offsetHeight / nlines;
-      else return measure.firstChild.offsetHeight || 1;
+      if (nlines) return (lineDiv.offsetHeight / nlines) || 1;
+      measure.innerHTML = "<pre>x</pre>";
+      return measure.firstChild.offsetHeight || 1;
     }
-    function charWidth() {return (measure.firstChild.offsetWidth || 320) / 40;}
     function paddingTop() {return lineSpace.offsetTop;}
     function paddingLeft() {return lineSpace.offsetLeft;}
 
     function posFromMouse(e, liberal) {
-      var off = eltOffset(lineSpace),
-          x = e.pageX() - off.left,
-          y = e.pageY() - off.top;
-      if (!liberal && e.target() != lineSpace.parentNode && !(e.target() == wrapper && y > (lines.length * lineHeight())))
-        for (var n = e.target(); n != lineDiv && n != cursor; n = n.parentNode)
-          if (!n || n == wrapper) return null;
-      var line = showingFrom + Math.floor(y / lineHeight());
-      return clipPos({line: line, ch: charFromX(clipLine(line), x)});
+      var offW = eltOffset(scroller, true), x, y;
+      // Fails unpredictably on IE[67] when mouse is dragged around quickly.
+      try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
+      // This is a mess of a heuristic to try and determine whether a
+      // scroll-bar was clicked or not, and to return null if one was
+      // (and !liberal).
+      if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
+        return null;
+      var offL = eltOffset(lineSpace, true);
+      var line = showingFrom + Math.floor((y - offL.top) / lineHeight());
+      return clipPos({line: line, ch: charFromX(clipLine(line), x - offL.left)});
     }
     function onContextMenu(e) {
       var pos = posFromMouse(e);
       if (!pos || window.opera) return; // Opera is difficult.
       if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
-        setCursor(pos.line, pos.ch);
+        operation(setCursor)(pos.line, pos.ch);
 
       var oldCSS = input.style.cssText;
-      input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.pageY() - 1) +
-        "px; left: " + (e.pageX() - 1) + "px; z-index: 1000; background: white; " +
-        "border-width: 0; outline: none; overflow: hidden;";
+      inputDiv.style.position = "absolute";
+      input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
+        "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
+        "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
+      leaveInputAlone = true;
       var val = input.value = getSelection();
-      input.focus();
-      setSelRange(input, 0, val.length);
-      if (gecko) e.stop();
-      leaveInputAlone = true;
-      setTimeout(function() {
-        if (input.value != val) operation(replaceSelection)(input.value, "end");
+      focusInput();
+      setSelRange(input, 0, input.value.length);
+      function rehide() {
+        var newVal = splitLines(input.value).join("\n");
+        if (newVal != val) operation(replaceSelection)(newVal, "end");
+        inputDiv.style.position = "relative";
         input.style.cssText = oldCSS;
         leaveInputAlone = false;
         prepareInput();
         slowPoll();
-      }, 50);
+      }
+
+      if (gecko) {
+        e_stop(e);
+        var mouseup = connect(window, "mouseup", function() {
+          mouseup();
+          setTimeout(rehide, 20);
+        }, true);
+      }
+      else {
+        setTimeout(rehide, 50);
+      }
     }
 
     // Cursor-blinking
@@ -1120,19 +1376,18 @@
           }
         }
       }
-      for (var i = head.line, e = forward ? Math.min(i + 50, lines.length) : Math.max(0, i - 50); i != e; i+=d) {
+      for (var i = head.line, e = forward ? Math.min(i + 100, lines.length) : Math.max(-1, i - 100); i != e; i+=d) {
         var line = lines[i], first = i == head.line;
         var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
-        if (found) {
-          var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
-          var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
-              two = markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
-          var clear = operation(function(){one(); two();});
-          if (autoclear) setTimeout(clear, 800);
-          else bracketHighlighted = clear;
-          break;
-        }
+        if (found) break;
       }
+      if (!found) found = {pos: null, match: false};
+      var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
+      var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
+          two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
+      var clear = operation(function(){one.clear(); two && two.clear();});
+      if (autoclear) setTimeout(clear, 800);
+      else bracketHighlighted = clear;
     }
 
     // Finds the line to start with when starting a parse. Tries to
@@ -1148,7 +1403,7 @@
         if (line.stateAfter) return search;
         var indented = line.indentation();
         if (minline == null || minindent > indented) {
-          minline = search;
+          minline = search - 1;
           minindent = indented;
         }
       }
@@ -1163,11 +1418,21 @@
         line.highlight(mode, state);
         line.stateAfter = copyState(mode, state);
       }
-      if (!lines[n].stateAfter) work.push(n);
+      changes.push({from: start, to: n});
+      if (n < lines.length && !lines[n].stateAfter) work.push(n);
       return state;
     }
+    function highlightLines(start, end) {
+      var state = getStateBefore(start);
+      for (var i = start; i < end; ++i) {
+        var line = lines[i];
+        line.highlight(mode, state);
+        line.stateAfter = copyState(mode, state);
+      }
+    }
     function highlightWorker() {
       var end = +new Date + options.workTime;
+      var foundWork = work.length;
       while (work.length) {
         if (!lines[showingFrom].stateAfter) var task = showingFrom;
         else var task = work.pop();
@@ -1176,20 +1441,29 @@
         if (state) state = copyState(mode, state);
         else state = startState(mode);
 
+        var unchanged = 0, compare = mode.compareStates, realChange = false;
         for (var i = start, l = lines.length; i < l; ++i) {
           var line = lines[i], hadState = line.stateAfter;
           if (+new Date > end) {
             work.push(i);
             startWorker(options.workDelay);
-            changes.push({from: task, to: i});
+            if (realChange) changes.push({from: task, to: i + 1});
             return;
           }
           var changed = line.highlight(mode, state);
+          if (changed) realChange = true;
           line.stateAfter = copyState(mode, state);
-          if (hadState && !changed && line.text) break;
+          if (compare) {
+            if (hadState && compare(hadState, state)) break;
+          } else {
+            if (changed !== false || !hadState) unchanged = 0;
+            else if (++unchanged > 3) break;
+          }
         }
-        changes.push({from: task, to: i});
+        if (realChange) changes.push({from: task, to: i + 1});
       }
+      if (foundWork && options.onHighlightComplete)
+        options.onHighlightComplete(instance);
     }
     function startWorker(time) {
       if (!work.length) return;
@@ -1207,24 +1481,29 @@
       var reScroll = false;
       if (selectionChanged) reScroll = !scrollCursorIntoView();
       if (changes.length) updateDisplay(changes);
-      else if (selectionChanged) updateCursor();
+      else {
+        if (selectionChanged) updateCursor();
+        if (gutterDirty) updateGutter();
+      }
       if (reScroll) scrollCursorIntoView();
-      if (selectionChanged) restartBlink();
+      if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
 
       // updateInput can be set to a boolean value to force/prevent an
       // update.
-      if (!leaveInputAlone && (updateInput === true || (updateInput !== false && selectionChanged)))
+      if (focused && !leaveInputAlone &&
+          (updateInput === true || (updateInput !== false && selectionChanged)))
         prepareInput();
 
-      if (selectionChanged && options.onCursorActivity)
-        options.onCursorActivity(instance);
-      if (textChanged && options.onChange)
-        options.onChange(instance);
       if (selectionChanged && options.matchBrackets)
         setTimeout(operation(function() {
           if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
           matchBrackets(false);
         }), 20);
+      var tc = textChanged; // textChanged can be reset by cursoractivity callback
+      if (selectionChanged && options.onCursorActivity)
+        options.onCursorActivity(instance);
+      if (tc && options.onChange && instance)
+        options.onChange(instance, tc);
     }
     var nestedOperation = 0;
     function operation(f) {
@@ -1259,6 +1538,7 @@
               var newmatch = line.match(query);
               if (newmatch) match = newmatch;
               else break;
+              start++;
             }
           }
           else {
@@ -1338,9 +1618,21 @@
       },
 
       from: function() {if (this.atOccurrence) return copyPos(this.pos.from);},
-      to: function() {if (this.atOccurrence) return copyPos(this.pos.to);}
+      to: function() {if (this.atOccurrence) return copyPos(this.pos.to);},
+
+      replace: function(newText) {
+        var self = this;
+        if (this.atOccurrence)
+          operation(function() {
+            self.pos.to = replaceRange(newText, self.pos.from, self.pos.to);
+          })();
+      }
     };
 
+    for (var ext in extensions)
+      if (extensions.propertyIsEnumerable(ext) &&
+          !instance.propertyIsEnumerable(ext))
+        instance[ext] = extensions[ext];
     return instance;
   } // (end of function CodeMirror)
 
@@ -1348,6 +1640,7 @@
   CodeMirror.defaults = {
     value: "",
     mode: null,
+    theme: "default",
     indentUnit: 2,
     indentWithTabs: false,
     tabMode: "classic",
@@ -1356,17 +1649,21 @@
     onKeyEvent: null,
     lineNumbers: false,
     gutter: false,
+    fixedGutter: false,
     firstLineNumber: 1,
     readOnly: false,
+    smartHome: true,
     onChange: null,
     onCursorActivity: null,
     onGutterClick: null,
+    onHighlightComplete: null,
     onFocus: null, onBlur: null, onScroll: null,
     matchBrackets: false,
     workTime: 100,
     workDelay: 200,
     undoDepth: 40,
-    tabindex: null
+    tabindex: null,
+    document: window.document
   };
 
   // Known modes, by name and by MIME
@@ -1383,15 +1680,15 @@
       spec = mimeModes[spec];
     if (typeof spec == "string")
       var mname = spec, config = {};
-    else
+    else if (spec != null)
       var mname = spec.name, config = spec;
     var mfactory = modes[mname];
     if (!mfactory) {
       if (window.console) console.warn("No mode " + mname + " found, falling back to plain text.");
       return CodeMirror.getMode(options, "text/plain");
     }
-    return mfactory(options, config);
-  }
+    return mfactory(options, config || {});
+  };
   CodeMirror.listModes = function() {
     var list = [];
     for (var m in modes)
@@ -1401,10 +1698,15 @@
   CodeMirror.listMIMEs = function() {
     var list = [];
     for (var m in mimeModes)
-      if (mimeModes.propertyIsEnumerable(m)) list.push(m);
+      if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
     return list;
   };
 
+  var extensions = {};
+  CodeMirror.defineExtension = function(name, func) {
+    extensions[name] = func;
+  };
+
   CodeMirror.fromTextArea = function(textarea, options) {
     if (!options) options = {};
     options.value = textarea.value;
@@ -1484,7 +1786,7 @@
       if (ok) {++this.pos; return ch;}
     },
     eatWhile: function(match) {
-      var start = this.start;
+      var start = this.pos;
       while (this.eat(match)){}
       return this.pos > start;
     },
@@ -1517,6 +1819,7 @@
     },
     current: function(){return this.string.slice(this.start, this.pos);}
   };
+  CodeMirror.StringStream = StringStream;
 
   // Line objects. These hold state related to a line, including
   // highlighting info (the styles array).
@@ -1526,10 +1829,23 @@
     this.text = text;
     this.marked = this.gutterMarker = this.className = null;
   }
+  Line.inheritMarks = function(text, orig) {
+    var ln = new Line(text), mk = orig.marked;
+    if (mk) {
+      for (var i = 0; i < mk.length; ++i) {
+        if (mk[i].to == null) {
+          var newmk = ln.marked || (ln.marked = []), mark = mk[i];
+          newmk.push({from: null, to: null, style: mark.style, set: mark.set});
+          mark.set.push(ln);
+        }
+      }
+    }
+    return ln;
+  }
   Line.prototype = {
     // Replace a piece of a line, keeping the styles around it intact.
-    replace: function(from, to, text) {
-      var st = [], mk = this.marked;
+    replace: function(from, to_, text) {
+      var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
       copyStyles(0, from, this.styles, st);
       if (text) st.push(text, null);
       copyStyles(to, this.text.length, this.styles, st);
@@ -1538,39 +1854,86 @@
       this.stateAfter = null;
       if (mk) {
         var diff = text.length - (to - from), end = this.text.length;
-        function fix(n) {return n <= Math.min(to, to + diff) ? n : n + diff;}
+        var changeStart = Math.min(from, from + diff);
         for (var i = 0; i < mk.length; ++i) {
           var mark = mk[i], del = false;
-          if (mark.from >= end) del = true;
-          else {mark.from = fix(mark.from); if (mark.to != null) mark.to = fix(mark.to);}
-          if (del || mark.from >= mark.to) {mk.splice(i, 1); i--;}
+          if (mark.from != null && mark.from >= end) del = true;
+          else {
+            if (mark.from != null && mark.from >= from) {
+              mark.from += diff;
+              if (mark.from <= 0) mark.from = from == null ? null : 0;
+            }
+            else if (to_ == null) mark.to = null;
+            if (mark.to != null && mark.to > from) {
+              mark.to += diff;
+              if (mark.to < 0) del = true;
+            }
+          }
+          if (del || (mark.from != null && mark.to != null && mark.from >= mark.to)) mk.splice(i--, 1);
         }
       }
     },
-    // Split a line in two, again keeping styles intact.
+    // Split a part off a line, keeping styles and markers intact.
     split: function(pos, textBefore) {
-      var st = [textBefore, null];
+      var st = [textBefore, null], mk = this.marked;
       copyStyles(pos, this.text.length, this.styles, st);
-      return new Line(textBefore + this.text.slice(pos), st);
+      var taken = new Line(textBefore + this.text.slice(pos), st);
+      if (mk) {
+        for (var i = 0; i < mk.length; ++i) {
+          var mark = mk[i];
+          if (mark.to > pos || mark.to == null) {
+            if (!taken.marked) taken.marked = [];
+            taken.marked.push({
+              from: mark.from < pos || mark.from == null ? null : mark.from - pos + textBefore.length,
+              to: mark.to == null ? null : mark.to - pos + textBefore.length,
+              style: mark.style, set: mark.set
+            });
+            mark.set.push(taken);
+          }
+        }
+      }
+      return taken;
     },
-    addMark: function(from, to, style) {
-      var mk = this.marked, mark = {from: from, to: to, style: style};
+    append: function(line) {
+      if (!line.text.length) return;
+      var mylen = this.text.length, mk = line.marked;
+      this.text += line.text;
+      copyStyles(0, line.text.length, line.styles, this.styles);
+      if (mk && mk.length) {
+        var mymk = this.marked || (this.marked = []);
+        for (var i = 0; i < mymk.length; ++i)
+          if (mymk[i].to == null) mymk[i].to = mylen;
+        outer: for (var i = 0; i < mk.length; ++i) {
+          var mark = mk[i];
+          if (!mark.from) {
+            for (var j = 0; j < mymk.length; ++j) {
+              var mymark = mymk[j];
+              if (mymark.to == mylen && mymark.set == mark.set) {
+                mymark.to = mark.to == null ? null : mark.to + mylen;
+                continue outer;
+              }
+            }
+          }
+          mymk.push(mark);
+          mark.set.push(this);
+          mark.from += mylen;
+          if (mark.to != null) mark.to += mylen;
+        }
+      }
+    },
+    addMark: function(from, to, style, set) {
+      set.push(this);
       if (this.marked == null) this.marked = [];
-      this.marked.push(mark);
-      this.marked.sort(function(a, b){return a.from - b.from;});
-      return mark;
-    },
-    removeMark: function(mark) {
-      var mk = this.marked;
-      if (!mk) return;
-      for (var i = 0; i < mk.length; ++i)
-        if (mk[i] == mark) {mk.splice(i, 1); break;}
+      this.marked.push({from: from, to: to, style: style, set: set});
+      this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
     },
     // Run the given mode's parser over a line, update the styles
     // array, which contains alternating fragments of text and CSS
     // classes.
     highlight: function(mode, state) {
-      var stream = new StringStream(this.text), st = this.styles, pos = 0, changed = false;
+      var stream = new StringStream(this.text), st = this.styles, pos = 0;
+      var changed = false, curWord = st[0], prevWord;
+      if (this.text == "" && mode.blankLine) mode.blankLine(state);
       while (!stream.eol()) {
         var style = mode.token(stream, state);
         var substr = this.text.slice(stream.start, stream.pos);
@@ -1578,8 +1941,9 @@
         if (pos && st[pos-1] == style)
           st[pos-2] += substr;
         else if (substr) {
-          if (!changed && st[pos] != substr || st[pos+1] != style) changed = true;
+          if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;
           st[pos++] = substr; st[pos++] = style;
+          prevWord = curWord; curWord = st[pos];
         }
         // Give up when line is ridiculously long
         if (stream.pos > 5000) {
@@ -1588,7 +1952,11 @@
         }
       }
       if (st.length != pos) {st.length = pos; changed = true;}
-      return changed;
+      if (pos && st[pos-2] != prevWord) changed = true;
+      // Short lines with simple highlights return null, and are
+      // counted as changed by the driver because they are likely to
+      // highlight the same way in various contexts.
+      return changed || (st.length < 5 && this.text.length < 10 ? null : false);
     },
     // Fetch the parser token for a given character. Useful for hacks
     // that want to inspect the mode state (say, for completion).
@@ -1607,7 +1975,7 @@
     indentation: function() {return countColumn(this.text);},
     // Produces an HTML fragment for the line, taking selection,
     // marking, and highlighting into account.
-    getHTML: function(sfrom, sto, includePre) {
+    getHTML: function(sfrom, sto, includePre, endAt) {
       var html = [];
       if (includePre)
         html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
@@ -1618,11 +1986,18 @@
       }
       var st = this.styles, allText = this.text, marked = this.marked;
       if (sfrom == sto) sfrom = null;
+      var len = allText.length;
+      if (endAt != null) len = Math.min(endAt, len);
 
-      if (!allText)
+      if (!allText && endAt == null)
         span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
       else if (!marked && sfrom == null)
-        for (var i = 0, e = st.length; i < e; i+=2) span(st[i], st[i+1]);
+        for (var i = 0, ch = 0; ch < len; i+=2) {
+          var str = st[i], style = st[i+1], l = str.length;
+          if (ch + l > len) str = str.slice(0, len - ch);
+          ch += l;
+          span(str, style && "cm-" + style);
+        }
       else {
         var pos = 0, i = 0, text = "", style, sg = 0;
         var markpos = -1, mark = null;
@@ -1632,9 +2007,9 @@
             mark = (markpos < marked.length) ? marked[markpos] : null;
           }
         }
-        nextMark();        
-        while (pos < allText.length) {
-          var upto = allText.length;
+        nextMark();
+        while (pos < len) {
+          var upto = len;
           var extraStyle = "";
           if (sfrom != null) {
             if (sfrom > pos) upto = sfrom;
@@ -1653,12 +2028,12 @@
           }
           for (;;) {
             var end = pos + text.length;
-            var apliedStyle = style;
-            if (extraStyle) apliedStyle = style ? style + extraStyle : extraStyle;
-            span(end > upto ? text.slice(0, upto - pos) : text, apliedStyle);
+            var appliedStyle = style;
+            if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle;
+            span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
             if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
             pos = end;
-            text = st[i++]; style = st[i++];
+            text = st[i++]; style = "cm-" + st[i++];
           }
         }
         if (sfrom != null && sto == null) span(" ", "CodeMirror-selected");
@@ -1716,42 +2091,34 @@
     }
   };
 
-  // Event stopping compatibility wrapper.
-  function stopEvent() {
-    if (this.preventDefault) {this.preventDefault(); this.stopPropagation();}
-    else {this.returnValue = false; this.cancelBubble = true;}
-  }
+  function stopMethod() {e_stop(this);}
   // Ensure an event has a stop method.
   function addStop(event) {
-    if (!event.stop) event.stop = stopEvent;
+    if (!event.stop) event.stop = stopMethod;
     return event;
   }
 
-  // Event wrapper, exposing the few operations we need.
-  function Event(orig) {this.e = orig;}
-  Event.prototype = {
-    stop: function() {stopEvent.call(this.e);},
-    target: function() {return this.e.target || this.e.srcElement;},
-    button: function() {
-      if (this.e.which) return this.e.which;
-      else if (this.e.button & 1) return 1;
-      else if (this.e.button & 2) return 3;
-      else if (this.e.button & 4) return 2;
-    },
-    pageX: function() {
-      if (this.e.pageX != null) return this.e.pageX;
-      else return this.e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
-    },
-    pageY: function() {
-      if (this.e.pageY != null) return this.e.pageY;
-      else return this.e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
-    }
-  };
+  function e_preventDefault(e) {
+    if (e.preventDefault) e.preventDefault();
+    else e.returnValue = false;
+  }
+  function e_stopPropagation(e) {
+    if (e.stopPropagation) e.stopPropagation();
+    else e.cancelBubble = true;
+  }
+  function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
+  function e_target(e) {return e.target || e.srcElement;}
+  function e_button(e) {
+    if (e.which) return e.which;
+    else if (e.button & 1) return 1;
+    else if (e.button & 2) return 3;
+    else if (e.button & 4) return 2;
+  }
 
   // Event handler registration. If disconnect is true, it'll return a
   // function that unregisters the handler.
   function connect(node, type, handler, disconnect) {
-    function wrapHandler(event) {handler(new Event(event || window.event));}
+    function wrapHandler(event) {handler(event || window.event);}
     if (typeof node.addEventListener == "function") {
       node.addEventListener(type, wrapHandler, false);
       if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);};
@@ -1772,7 +2139,18 @@
     pre.innerHTML = " "; return !pre.innerHTML;
   })();
 
+  // Detect drag-and-drop
+  var dragAndDrop = (function() {
+    // IE8 has ondragstart and ondrop properties, but doesn't seem to
+    // actually support ondragstart the way it's supposed to work.
+    if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false;
+    var div = document.createElement('div');
+    return "ondragstart" in div && "ondrop" in div;
+  })();
+
   var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
+  var ie = /MSIE \d/.test(navigator.userAgent);
+  var safari = /Apple Computer/.test(navigator.vendor);
 
   var lineSep = "\n";
   // Feature-detect whether newlines in textareas are converted to \r\n
@@ -1802,11 +2180,23 @@
     return n;
   }
 
+  function computedStyle(elt) {
+    if (elt.currentStyle) return elt.currentStyle;
+    return window.getComputedStyle(elt, null);
+  }
   // Find the position of an element by following the offsetParent chain.
-  function eltOffset(node) {
-    var x = 0, y = 0, n2 = node;
-    for (var n = node; n; n = n.offsetParent) {x += n.offsetLeft; y += n.offsetTop;}
-    for (var n = node; n != document.body; n = n.parentNode) {x -= n.scrollLeft; y -= n.scrollTop;}
+  // If screen==true, it returns screen (rather than page) coordinates.
+  function eltOffset(node, screen) {
+    var doc = node.ownerDocument.body;
+    var x = 0, y = 0, skipDoc = false;
+    for (var n = node; n; n = n.offsetParent) {
+      x += n.offsetLeft; y += n.offsetTop;
+      if (screen && computedStyle(n).position == "fixed")
+        skipDoc = true;
+    }
+    var e = screen && !skipDoc ? null : doc;
+    for (var n = node.parentNode; n != e; n = n.parentNode)
+      if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}
     return {left: x, top: y};
   }
   // Get a node's text content.
@@ -1819,9 +2209,18 @@
   function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
   function copyPos(x) {return {line: x.line, ch: x.ch};}
 
+  var escapeElement = document.createElement("pre");
   function htmlEscape(str) {
-    return str.replace(/[<&]/g, function(str) {return str == "&" ? "&amp;" : "&lt;";});
+    if (badTextContent) {
+      escapeElement.innerHTML = "";
+      escapeElement.appendChild(document.createTextNode(str));
+    } else {
+      escapeElement.textContent = str;
+    }
+    return escapeElement.innerHTML;
   }
+  var badTextContent = htmlEscape("\t") != "\t";
+  CodeMirror.htmlEscape = htmlEscape;
 
   // Used to position the cursor after an undo/redo by finding the
   // last edited character.
@@ -1842,8 +2241,9 @@
 
   // See if "".split is the broken IE version, if so, provide an
   // alternative way to split lines.
+  var splitLines, selRange, setSelRange;
   if ("\n\nb".split(/\n/).length != 3)
-    var splitLines = function(string) {
+    splitLines = function(string) {
       var pos = 0, nl, result = [];
       while ((nl = string.indexOf("\n", pos)) > -1) {
         result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
@@ -1853,23 +2253,40 @@
       return result;
     };
   else
-    var splitLines = function(string){return string.split(/\r?\n/);};
+    splitLines = function(string){return string.split(/\r?\n/);};
+  CodeMirror.splitLines = splitLines;
 
   // Sane model of finding and setting the selection in a textarea
   if (window.getSelection) {
-    var selRange = function(te) {
+    selRange = function(te) {
       try {return {start: te.selectionStart, end: te.selectionEnd};}
       catch(e) {return null;}
     };
-    var setSelRange = function(te, start, end) {
-      try {te.setSelectionRange(start, end);}
-      catch(e) {} // Fails on Firefox when textarea isn't part of the document
-    };
+    if (safari)
+      // On Safari, selection set with setSelectionRange are in a sort
+      // of limbo wrt their anchor. If you press shift-left in them,
+      // the anchor is put at the end, and the selection expanded to
+      // the left. If you press shift-right, the anchor ends up at the
+      // front. This is not what CodeMirror wants, so it does a
+      // spurious modify() call to get out of limbo.
+      setSelRange = function(te, start, end) {
+        if (start == end)
+          te.setSelectionRange(start, end);
+        else {
+          te.setSelectionRange(start, end - 1);
+          window.getSelection().modify("extend", "forward", "character");
+        }
+      };
+    else
+      setSelRange = function(te, start, end) {
+        try {te.setSelectionRange(start, end);}
+        catch(e) {} // Fails on Firefox when textarea isn't part of the document
+      };
   }
   // IE model. Don't ask.
   else {
-    var selRange = function(te) {
-      try {var range = document.selection.createRange();}
+    selRange = function(te) {
+      try {var range = te.ownerDocument.selection.createRange();}
       catch(e) {return null;}
       if (!range || range.parentElement() != te) return null;
       var val = te.value, len = val.length, localRange = te.createTextRange();
@@ -1890,7 +2307,7 @@
       for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {}
       return {start: start, end: end};
     };
-    var setSelRange = function(te, start, end) {
+    setSelRange = function(te, start, end) {
       var range = te.createTextRange();
       range.collapse(true);
       var endrange = range.duplicate();
--- a/rhodecode/public/js/css_browser_selector.js	Sun Feb 19 20:21:14 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
-CSS Browser Selector v0.3.5 (Feb 05, 2010)
-Rafael Lima (http://rafael.adm.br)
-http://rafael.adm.br/css_browser_selector
-License: http://creativecommons.org/licenses/by/2.5/
-Contributors: http://rafael.adm.br/css_browser_selector#contributors
-2. Set CSS attributes with the code of each browser/os you want to hack
-
-Examples:
-
-    * html.gecko div#header { margin: 1em; }
-    * .opera #header { margin: 1.2em; }
-    * .ie .mylink { font-weight: bold; }
-    * .mac.ie .mylink { font-weight: bold; }
-    * .[os].[browser] .mylink { font-weight: bold; } -> without space between .[os] and .[browser]
-
-Available OS Codes [os]:
-
-    * win - Microsoft Windows
-    * linux - Linux (x11 and linux)
-    * mac - Mac OS
-    * freebsd - FreeBSD
-    * ipod - iPod Touch
-    * iphone - iPhone
-    * webtv - WebTV
-    * mobile - J2ME Devices (ex: Opera mini)
-
-Available Browser Codes [browser]:
-
-    * ie - Internet Explorer (All versions)
-    * ie8 - Internet Explorer 8.x
-    * ie7 - Internet Explorer 7.x
-    * ie6 - Internet Explorer 6.x
-    * ie5 - Internet Explorer 5.x
-    * gecko - Mozilla, Firefox (all versions), Camino
-    * ff2 - Firefox 2
-    * ff3 - Firefox 3
-    * ff3_5 - Firefox 3.5 new
-    * opera - Opera (All versions)
-    * opera8 - Opera 8.x
-    * opera9 - Opera 9.x
-    * opera10 - Opera 10.x
-    * konqueror - Konqueror
-    * webkit or safari - Safari, NetNewsWire, OmniWeb, Shiira, Google Chrome
-    * safari3 - Safari 3.x
-    * chrome - Google Chrome
-    * iron - SRWare Iron new
-
-*/
-function css_browser_selector(u){var ua = u.toLowerCase(),is=function(t){return ua.indexOf(t)>-1;},g='gecko',w='webkit',s='safari',o='opera',h=document.documentElement,b=[(!(/opera|webtv/i.test(ua))&&/msie\s(\d)/.test(ua))?('ie ie'+RegExp.$1):is('firefox/2')?g+' ff2':is('firefox/3.5')?g+' ff3 ff3_5':is('firefox/3')?g+' ff3':is('gecko/')?g:is('opera')?o+(/version\/(\d+)/.test(ua)?' '+o+RegExp.$1:(/opera(\s|\/)(\d+)/.test(ua)?' '+o+RegExp.$2:'')):is('konqueror')?'konqueror':is('chrome')?w+' chrome':is('iron')?w+' iron':is('applewebkit/')?w+' '+s+(/version\/(\d+)/.test(ua)?' '+s+RegExp.$1:''):is('mozilla/')?g:'',is('j2me')?'mobile':is('iphone')?'iphone':is('ipod')?'ipod':is('mac')?'mac':is('darwin')?'mac':is('webtv')?'webtv':is('win')?'win':is('freebsd')?'freebsd':(is('x11')||is('linux'))?'linux':'','js']; c = b.join(' '); h.className += ' '+c; return c;}; css_browser_selector(navigator.userAgent);
--- a/rhodecode/public/js/graph.js	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/public/js/graph.js	Sun Feb 26 17:25:09 2012 +0200
@@ -63,18 +63,16 @@
 		var rela = document.getElementById('graph');
 		var pad = pad;
 		var scale = 22;
-		
+
 		for (var i in data) {
 			this.scale(scale);
+
 			var row = document.getElementById("chg_"+idx);
-			var	next = document.getElementById("chg_"+idx+1);
+			if (row == null)
+				continue;
+			var	next = document.getElementById("chg_"+(idx+1));
 			var extra = 0;
 			
-			//skip this since i don't have DATE in my app
-			//if (next.is('.changesets-date')) {
-			//	extra = next.outerHeight();
-			//}
-						
 			this.cell[1] += row.clientWidth;
 			this.bg[1] += this.bg_height;
 			
@@ -82,7 +80,10 @@
 			nodeid = cur[0];
 			node = cur[1];
 			in_l = cur[2];
-			
+
+			var rowY = row.offsetTop + row.offsetHeight/2 - rela.offsetTop;
+			var nextY = (next == null) ? rowY + row.offsetHeight/2 : next.offsetTop + next.offsetHeight/2 - rela.offsetTop;
+
 			for (var j in in_l) {
 				
 				line = in_l[j];
@@ -99,17 +100,26 @@
 				}
 				
 				this.setColor(color, 0.0, 0.65);
+
 				
-				y = row.offsetTop-rela.offsetTop+4;
 				x = pad-((this.cell[0] + this.box_size * start - 1) + this.bg_height-2);
 				
 				this.ctx.lineWidth=this.line_width;
 				this.ctx.beginPath();
-				this.ctx.moveTo(x, y);
+				this.ctx.moveTo(x, rowY);
 
-				y += row.offsetHeight;
-				x = pad-((1 + this.box_size * end) + this.bg_height-2);
-				this.ctx.lineTo(x,y+extra,3);
+				
+				if (start == end)
+				{
+					x = pad-((1 + this.box_size * end) + this.bg_height-2);
+					this.ctx.lineTo(x,nextY+extra,3);
+				}
+				else
+				{
+					var x2 = pad-((1 + this.box_size * end) + this.bg_height-2);
+					var ymid = (rowY+nextY) / 2;
+					this.ctx.bezierCurveTo (x,ymid,x2,ymid,x2,nextY);
+				}
 				this.ctx.stroke();
 			}
 			
@@ -117,12 +127,12 @@
 			color = node[1]
 			
 			radius = this.dot_radius;
-			y = row.offsetTop-rela.offsetTop+4;
+
 			x = pad-(Math.round(this.cell[0] * scale/2 * column + radius) + 15 - (column*4));
 		
 			this.ctx.beginPath();
 			this.setColor(color, 0.25, 0.75);
-			this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
+			this.ctx.arc(x, rowY, radius, 0, Math.PI * 2, true);
 			this.ctx.fill();
 			
 			idx++;
--- a/rhodecode/public/js/rhodecode.js	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/public/js/rhodecode.js	Sun Feb 26 17:25:09 2012 +0200
@@ -7,10 +7,10 @@
 }
 
 
-function str_repeat(i, m) {
+var str_repeat = function(i, m) {
 	for (var o = []; m > 0; o[--m] = i);
 	return o.join('');
-}
+};
 
 /**
  * INJECT .format function into String
@@ -55,7 +55,7 @@
  * 
  * @returns {ColorGenerator}
  */
-function ColorGenerator(){
+var ColorGenerator = function(){
 	this.GOLDEN_RATIO = 0.618033988749895;
 	this.CURRENT_RATIO = 0.22717784590367374 // this can be random
 	this.HSV_1 = 0.75;//saturation
@@ -129,7 +129,21 @@
 				/* disable for the mercury iOS browser, or at least older versions of the webkit engine */
 				|| (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
 		)
-)
+);
+
+var _run_callbacks = function(callbacks){
+	if (callbacks !== undefined){
+		var _l = callbacks.length;
+	    for (var i=0;i<_l;i++){
+	    	var func = callbacks[i];
+	    	if(typeof(func)=='function'){
+	            try{
+	          	    func();
+	            }catch (err){};            		
+	    	}
+	    }
+	}		
+}
 
 /**
  * Partial Ajax Implementation
@@ -172,11 +186,14 @@
 	YUC.asyncRequest(method,url,{
 		success:s_wrapper,
 		failure:function(o){
-			console.log(o)
+			console.log(o);
+			YUD.get(container).innerHTML='ERROR';
+			YUD.setStyle(container,'opacity','1.0');
+			YUD.setStyle(container,'color','red');
 		}
 	},args);
 	
-}
+};
 
 /**
  * tooltip activate
@@ -203,7 +220,7 @@
         hidedelay:5,
         showdelay:20,
     });
-}
+};
 
 /**
  * show more
@@ -214,5 +231,492 @@
         YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
         YUD.setStyle(el.parentNode,'display','none');
     });
+};
+
+
+/**
+ * Quick filter widget
+ * 
+ * @param target: filter input target
+ * @param nodes: list of nodes in html we want to filter.
+ * @param display_element function that takes current node from nodes and
+ *    does hide or show based on the node
+ * 
+ */
+var q_filter = function(target,nodes,display_element){
+	
+	var nodes = nodes;
+	var q_filter_field = YUD.get(target);
+	var F = YAHOO.namespace(target);
+
+	YUE.on(q_filter_field,'click',function(){
+	   q_filter_field.value = '';
+	});
+
+	YUE.on(q_filter_field,'keyup',function(e){
+	    clearTimeout(F.filterTimeout); 
+	    F.filterTimeout = setTimeout(F.updateFilter,600); 
+	});
+
+	F.filterTimeout = null;
+
+	var show_node = function(node){
+		YUD.setStyle(node,'display','')
+	}
+	var hide_node = function(node){
+		YUD.setStyle(node,'display','none');
+	}
+	
+	F.updateFilter  = function() { 
+	   // Reset timeout 
+	   F.filterTimeout = null;
+	   
+	   var obsolete = [];
+	   
+	   var req = q_filter_field.value.toLowerCase();
+	   
+	   var l = nodes.length;
+	   var i;
+	   var showing = 0;
+	   
+       for (i=0;i<l;i++ ){
+    	   var n = nodes[i];
+    	   var target_element = display_element(n)
+    	   if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
+    		   hide_node(target_element);
+    	   }
+    	   else{
+    		   show_node(target_element);
+    		   showing+=1;
+    	   }
+       }	  	   
+
+	   // if repo_count is set update the number
+	   var cnt = YUD.get('repo_count');
+	   if(cnt){
+		   YUD.get('repo_count').innerHTML = showing;
+	   }       
+       
+	}	
+};
+
+var ajaxPOST = function(url,postData,success) {
+    var sUrl = url;
+    var callback = {
+        success: success,
+        failure: function (o) {
+            alert("error");
+        },
+    };
+    var postData = postData;
+    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
+};
+
+
+/** comments **/
+var removeInlineForm = function(form) {
+	form.parentNode.removeChild(form);
+};
+
+var tableTr = function(cls,body){
+	var form = document.createElement('tr');
+	YUD.addClass(form, cls);
+	form.innerHTML = '<td class="lineno-inline new-inline"></td>'+
+    				 '<td class="lineno-inline old-inline"></td>'+ 
+                     '<td>{0}</td>'.format(body);
+	return form;
+};
+
+var createInlineForm = function(parent_tr, f_path, line) {
+	var tmpl = YUD.get('comment-inline-form-template').innerHTML;
+	tmpl = tmpl.format(f_path, line);
+	var form = tableTr('comment-form-inline',tmpl)
+	
+	// create event for hide button
+	form = new YAHOO.util.Element(form);
+	var form_hide_button = new YAHOO.util.Element(form.getElementsByClassName('hide-inline-form')[0]);
+	form_hide_button.on('click', function(e) {
+		var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
+		removeInlineForm(newtr);
+		YUD.removeClass(parent_tr, 'form-open');
+	});
+	return form
+};
+var injectInlineForm = function(tr){
+	  if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(tr,'no-comment')){
+		  return
+	  }	
+	  YUD.addClass(tr,'form-open');
+	  var node = tr.parentNode.parentNode.parentNode.getElementsByClassName('full_f_path')[0];
+	  var f_path = YUD.getAttribute(node,'path');
+	  var lineno = getLineNo(tr);
+	  var form = createInlineForm(tr, f_path, lineno);
+	  var target_tr = tr;
+	  if(YUD.hasClass(YUD.getNextSibling(tr),'inline-comments')){
+		  target_tr = YUD.getNextSibling(tr);
+	  }
+	  YUD.insertAfter(form,target_tr);
+	  YUD.get('text_'+lineno).focus();
+	  tooltip_activate();
+};
+
+var createInlineAddButton = function(tr,label){
+	var html = '<div class="add-comment"><span class="ui-btn">{0}</span></div>'.format(label);
+        
+	var add = new YAHOO.util.Element(tableTr('inline-comments-button',html));
+	add.on('click', function(e) {
+		injectInlineForm(tr);
+	});
+	return add;
+};
+
+var getLineNo = function(tr) {
+	var line;
+	var o = tr.children[0].id.split('_');
+	var n = tr.children[1].id.split('_');
+
+	if (n.length >= 2) {
+		line = n[n.length-1];
+	} else if (o.length >= 2) {
+		line = o[o.length-1];
+	}
+
+	return line
+};
+
+
+var fileBrowserListeners = function(current_url, node_list_url, url_base,
+									truncated_lbl, nomatch_lbl){
+	var current_url_branch = +"?branch=__BRANCH__";
+	var url = url_base;
+	var node_url = node_list_url;	
+
+	YUE.on('stay_at_branch','click',function(e){
+	    if(e.target.checked){
+	        var uri = current_url_branch;
+	        uri = uri.replace('__BRANCH__',e.target.value);
+	        window.location = uri;
+	    }
+	    else{
+	        window.location = current_url;
+	    }
+	})            
+
+	var n_filter = YUD.get('node_filter');
+	var F = YAHOO.namespace('node_filter');
+	
+	F.filterTimeout = null;
+	var nodes = null;
+
+	F.initFilter = function(){
+	  YUD.setStyle('node_filter_box_loading','display','');
+	  YUD.setStyle('search_activate_id','display','none');
+	  YUD.setStyle('add_node_id','display','none');
+	  YUC.initHeader('X-PARTIAL-XHR',true);
+	  YUC.asyncRequest('GET',url,{
+	      success:function(o){
+	        nodes = JSON.parse(o.responseText);
+	        YUD.setStyle('node_filter_box_loading','display','none');
+	        YUD.setStyle('node_filter_box','display','');
+	        n_filter.focus();
+			if(YUD.hasClass(n_filter,'init')){
+				n_filter.value = '';
+				YUD.removeClass(n_filter,'init');
+			}   
+	      },
+	      failure:function(o){
+	          console.log('failed to load');
+	      }
+	  },null);            
+	}
+
+	F.updateFilter  = function(e) {
+	    
+	    return function(){
+	        // Reset timeout 
+	        F.filterTimeout = null;
+	        var query = e.target.value.toLowerCase();
+	        var match = [];
+	        var matches = 0;
+	        var matches_max = 20;
+	        if (query != ""){
+	            for(var i=0;i<nodes.length;i++){
+	            	
+	                var pos = nodes[i].name.toLowerCase().indexOf(query)
+	                if(query && pos != -1){
+	                    
+	                    matches++
+	                    //show only certain amount to not kill browser 
+	                    if (matches > matches_max){
+	                        break;
+	                    }
+	                    
+	                    var n = nodes[i].name;
+	                    var t = nodes[i].type;
+	                    var n_hl = n.substring(0,pos)
+	                      +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
+	                      +n.substring(pos+query.length)                    
+	                    match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,node_url.replace('__FPATH__',n),n_hl));
+	                }
+	                if(match.length >= matches_max){
+	                    match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(truncated_lbl));
+	                }
+	                
+	            }                       
+	        }
+	        if(query != ""){
+	            YUD.setStyle('tbody','display','none');
+	            YUD.setStyle('tbody_filtered','display','');
+	            
+	            if (match.length==0){
+	              match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(nomatch_lbl));
+	            }                           
+	            
+	            YUD.get('tbody_filtered').innerHTML = match.join("");   
+	        }
+	        else{
+	            YUD.setStyle('tbody','display','');
+	            YUD.setStyle('tbody_filtered','display','none');
+	        }
+	        
+	    }
+	};
+
+	YUE.on(YUD.get('filter_activate'),'click',function(){
+	    F.initFilter();
+	})
+	YUE.on(n_filter,'click',function(){
+		if(YUD.hasClass(n_filter,'init')){
+			n_filter.value = '';
+			YUD.removeClass(n_filter,'init');
+		}
+	 });
+	YUE.on(n_filter,'keyup',function(e){
+	    clearTimeout(F.filterTimeout); 
+	    F.filterTimeout = setTimeout(F.updateFilter(e),600);
+	});
+};
+
+
+var initCodeMirror = function(textAreadId,resetUrl){  
+    var myCodeMirror = CodeMirror.fromTextArea(YUD.get(textAreadId),{
+           mode:  "null",
+           lineNumbers:true
+         });
+    YUE.on('reset','click',function(e){
+        window.location=resetUrl
+    });
+    
+    YUE.on('file_enable','click',function(){
+        YUD.setStyle('editor_container','display','');
+        YUD.setStyle('upload_file_container','display','none');
+        YUD.setStyle('filename_container','display','');
+    });
+    
+    YUE.on('upload_file_enable','click',function(){
+        YUD.setStyle('editor_container','display','none');
+        YUD.setStyle('upload_file_container','display','');
+        YUD.setStyle('filename_container','display','none');
+    });	
+};
+
+
+
+var getIdentNode = function(n){
+	//iterate thru nodes untill matched interesting node !
+	
+	if (typeof n == 'undefined'){
+		return -1
+	}
+	
+	if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
+			return n
+		}
+	else{
+		return getIdentNode(n.parentNode);
+	}
+};
+
+var  getSelectionLink = function(selection_link_label) {
+	return function(){
+	    //get selection from start/to nodes    	
+	    if (typeof window.getSelection != "undefined") {
+	    	s = window.getSelection();
+	
+	       	from = getIdentNode(s.anchorNode);
+	       	till = getIdentNode(s.focusNode);
+	        
+	        f_int = parseInt(from.id.replace('L',''));
+	        t_int = parseInt(till.id.replace('L',''));
+	        
+	        if (f_int > t_int){
+	        	//highlight from bottom 
+	        	offset = -35;
+	        	ranges = [t_int,f_int];
+	        	
+	        }
+	        else{
+	        	//highligth from top 
+	        	offset = 35;
+	        	ranges = [f_int,t_int];
+	        }
+	        
+	        if (ranges[0] != ranges[1]){
+	            if(YUD.get('linktt') == null){
+	                hl_div = document.createElement('div');
+	                hl_div.id = 'linktt';
+	            }
+	            anchor = '#L'+ranges[0]+'-'+ranges[1];
+	            hl_div.innerHTML = '';
+	            l = document.createElement('a');
+	            l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
+	            l.innerHTML = selection_link_label;
+	            hl_div.appendChild(l);
+	            
+	            YUD.get('body').appendChild(hl_div);
+	            
+	            xy = YUD.getXY(till.id);
+	            
+	            YUD.addClass('linktt','yui-tt');
+	            YUD.setStyle('linktt','top',xy[1]+offset+'px');
+	            YUD.setStyle('linktt','left',xy[0]+'px');
+	            YUD.setStyle('linktt','visibility','visible');
+	        }
+	        else{
+	        	YUD.setStyle('linktt','visibility','hidden');
+	        }
+	    }
+	}
+};
+
+var deleteNotification = function(url, notification_id,callbacks){
+    var callback = { 
+		success:function(o){
+		    var obj = YUD.get(String("notification_"+notification_id));
+		    if(obj.parentNode !== undefined){
+				obj.parentNode.removeChild(obj);
+			}
+			_run_callbacks(callbacks);
+		},
+	    failure:function(o){
+	        alert("error");
+	    },
+	};
+    var postData = '_method=delete';
+    var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
+    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, 
+    											  callback, postData);
+};	
+
+
+/**
+ * QUICK REPO MENU
+ */
+var quick_repo_menu = function(){
+    YUE.on(YUQ('.quick_repo_menu'),'click',function(e){
+        var menu = e.currentTarget.firstElementChild.firstElementChild;
+        if(YUD.hasClass(menu,'hidden')){
+            YUD.addClass(e.currentTarget,'active');
+            YUD.removeClass(menu,'hidden');
+        }else{
+            YUD.removeClass(e.currentTarget,'active');
+            YUD.addClass(menu,'hidden');
+        }
+    })
+};
+
+
+/**
+ * TABLE SORTING
+ */
+
+// returns a node from given html;
+var fromHTML = function(html){
+	  var _html = document.createElement('element');
+	  _html.innerHTML = html;
+	  return _html;
+}
+var get_rev = function(node){
+    var n = node.firstElementChild.firstElementChild;
+    
+    if (n===null){
+        return -1
+    }
+    else{
+        out = n.firstElementChild.innerHTML.split(':')[0].replace('r','');
+        return parseInt(out);
+    }
 }
 
+var get_name = function(node){
+	 var name = node.firstElementChild.children[2].innerHTML; 
+	 return name
+}
+var get_group_name = function(node){
+	var name = node.firstElementChild.children[1].innerHTML;
+	return name
+}
+var get_date = function(node){
+	var date_ = node.firstElementChild.innerHTML;
+	return date_
+}
+
+var revisionSort = function(a, b, desc, field) {
+	  
+	  var a_ = fromHTML(a.getData(field));
+	  var b_ = fromHTML(b.getData(field));
+	  
+	  // extract revisions from string nodes 
+	  a_ = get_rev(a_)
+	  b_ = get_rev(b_)
+	      	  
+	  var comp = YAHOO.util.Sort.compare;
+	  var compState = comp(a_, b_, desc);
+	  return compState;
+};
+var ageSort = function(a, b, desc, field) {
+    var a_ = a.getData(field);
+    var b_ = b.getData(field);
+    
+    var comp = YAHOO.util.Sort.compare;
+    var compState = comp(a_, b_, desc);
+    return compState;
+};
+
+var nameSort = function(a, b, desc, field) {
+    var a_ = fromHTML(a.getData(field));
+    var b_ = fromHTML(b.getData(field));
+    
+    // extract name from table
+    a_ = get_name(a_)
+    b_ = get_name(b_)          
+    
+    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));
+    
+    // extract name from table
+    a_ = get_group_name(a_)
+    b_ = get_group_name(b_)          
+    
+    var comp = YAHOO.util.Sort.compare;
+    var compState = comp(a_, b_, desc);
+    return compState;
+};
+var dateSort = function(a, b, desc, field) {
+    var a_ = fromHTML(a.getData(field));
+    var b_ = fromHTML(b.getData(field));
+    
+    // extract name from table
+    a_ = get_date(a_)
+    b_ = get_date(b_)          
+    
+    var comp = YAHOO.util.Sort.compare;
+    var compState = comp(a_, b_, desc);
+    return compState;
+};
\ No newline at end of file
--- a/rhodecode/public/js/yui.2.9.js	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/public/js/yui.2.9.js	Sun Feb 26 17:25:09 2012 +0200
@@ -101,4 +101,50 @@
 http://developer.yahoo.com/yui/license.html
 version: 2.9.0
 */
-(function(){var b=YAHOO.util.Event,g=YAHOO.lang,e=b.addListener,f=b.removeListener,c=b.getListeners,d=[],h={mouseenter:"mouseover",mouseleave:"mouseout"},a=function(n,m,l){var j=b._getCacheIndex(d,n,m,l),i,k;if(j>=0){i=d[j];}if(n&&i){k=f.call(b,i[0],m,i[3]);if(k){delete d[j][2];delete d[j][3];d.splice(j,1);}}return k;};g.augmentObject(b._specialTypes,h);g.augmentObject(b,{_createMouseDelegate:function(i,j,k){return function(q,m){var p=this,l=b.getRelatedTarget(q),o,n;if(p!=l&&!YAHOO.util.Dom.isAncestor(p,l)){o=p;if(k){if(k===true){o=j;}else{o=k;}}n=[q,j];if(m){n.splice(1,0,p,m);}return i.apply(o,n);}};},addListener:function(m,l,k,n,o){var i,j;if(h[l]){i=b._createMouseDelegate(k,n,o);i.mouseDelegate=true;d.push([m,l,k,i]);j=e.call(b,m,l,i);}else{j=e.apply(b,arguments);}return j;},removeListener:function(l,k,j){var i;if(h[k]){i=a.apply(b,arguments);}else{i=f.apply(b,arguments);}return i;},getListeners:function(p,o){var n=[],r,m=(o==="mouseover"||o==="mouseout"),q,k,j;if(o&&(m||h[o])){r=c.call(b,p,this._getType(o));if(r){for(k=r.length-1;k>-1;k--){j=r[k];q=j.fn.mouseDelegate;if((h[o]&&q)||(m&&!q)){n.push(j);}}}}else{n=c.apply(b,arguments);}return(n&&n.length)?n:null;}},true);b.on=b.addListener;}());YAHOO.register("event-mouseenter",YAHOO.util.Event,{version:"2.9.0",build:"2800"});
\ No newline at end of file
+(function(){var b=YAHOO.util.Event,g=YAHOO.lang,e=b.addListener,f=b.removeListener,c=b.getListeners,d=[],h={mouseenter:"mouseover",mouseleave:"mouseout"},a=function(n,m,l){var j=b._getCacheIndex(d,n,m,l),i,k;if(j>=0){i=d[j];}if(n&&i){k=f.call(b,i[0],m,i[3]);if(k){delete d[j][2];delete d[j][3];d.splice(j,1);}}return k;};g.augmentObject(b._specialTypes,h);g.augmentObject(b,{_createMouseDelegate:function(i,j,k){return function(q,m){var p=this,l=b.getRelatedTarget(q),o,n;if(p!=l&&!YAHOO.util.Dom.isAncestor(p,l)){o=p;if(k){if(k===true){o=j;}else{o=k;}}n=[q,j];if(m){n.splice(1,0,p,m);}return i.apply(o,n);}};},addListener:function(m,l,k,n,o){var i,j;if(h[l]){i=b._createMouseDelegate(k,n,o);i.mouseDelegate=true;d.push([m,l,k,i]);j=e.call(b,m,l,i);}else{j=e.apply(b,arguments);}return j;},removeListener:function(l,k,j){var i;if(h[k]){i=a.apply(b,arguments);}else{i=f.apply(b,arguments);}return i;},getListeners:function(p,o){var n=[],r,m=(o==="mouseover"||o==="mouseout"),q,k,j;if(o&&(m||h[o])){r=c.call(b,p,this._getType(o));if(r){for(k=r.length-1;k>-1;k--){j=r[k];q=j.fn.mouseDelegate;if((h[o]&&q)||(m&&!q)){n.push(j);}}}}else{n=c.apply(b,arguments);}return(n&&n.length)?n:null;}},true);b.on=b.addListener;}());YAHOO.register("event-mouseenter",YAHOO.util.Event,{version:"2.9.0",build:"2800"});
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var lang=YAHOO.lang,util=YAHOO.util,Ev=util.Event;util.DataSourceBase=function(oLiveData,oConfigs){if(oLiveData===null||oLiveData===undefined){return;}this.liveData=oLiveData;this._oQueue={interval:null,conn:null,requests:[]};this.responseSchema={};if(oConfigs&&(oConfigs.constructor==Object)){for(var sConfig in oConfigs){if(sConfig){this[sConfig]=oConfigs[sConfig];}}}var maxCacheEntries=this.maxCacheEntries;if(!lang.isNumber(maxCacheEntries)||(maxCacheEntries<0)){maxCacheEntries=0;}this._aIntervals=[];this.createEvent("cacheRequestEvent");this.createEvent("cacheResponseEvent");this.createEvent("requestEvent");this.createEvent("responseEvent");this.createEvent("responseParseEvent");this.createEvent("responseCacheEvent");this.createEvent("dataErrorEvent");this.createEvent("cacheFlushEvent");var DS=util.DataSourceBase;this._sName="DataSource instance"+DS._nIndex;DS._nIndex++;};var DS=util.DataSourceBase;lang.augmentObject(DS,{TYPE_UNKNOWN:-1,TYPE_JSARRAY:0,TYPE_JSFUNCTION:1,TYPE_XHR:2,TYPE_JSON:3,TYPE_XML:4,TYPE_TEXT:5,TYPE_HTMLTABLE:6,TYPE_SCRIPTNODE:7,TYPE_LOCAL:8,ERROR_DATAINVALID:"Invalid data",ERROR_DATANULL:"Null data",_nIndex:0,_nTransactionId:0,_cloneObject:function(o){if(!lang.isValue(o)){return o;}var copy={};if(Object.prototype.toString.apply(o)==="[object RegExp]"){copy=o;}else{if(lang.isFunction(o)){copy=o;}else{if(lang.isArray(o)){var array=[];for(var i=0,len=o.length;i<len;i++){array[i]=DS._cloneObject(o[i]);}copy=array;}else{if(lang.isObject(o)){for(var x in o){if(lang.hasOwnProperty(o,x)){if(lang.isValue(o[x])&&lang.isObject(o[x])||lang.isArray(o[x])){copy[x]=DS._cloneObject(o[x]);}else{copy[x]=o[x];}}}}else{copy=o;}}}}return copy;},_getLocationValue:function(field,context){var locator=field.locator||field.key||field,xmldoc=context.ownerDocument||context,result,res,value=null;try{if(!lang.isUndefined(xmldoc.evaluate)){result=xmldoc.evaluate(locator,context,xmldoc.createNSResolver(!context.ownerDocument?context.documentElement:context.ownerDocument.documentElement),0,null);while(res=result.iterateNext()){value=res.textContent;}}else{xmldoc.setProperty("SelectionLanguage","XPath");result=context.selectNodes(locator)[0];value=result.value||result.text||null;}return value;}catch(e){}},issueCallback:function(callback,params,error,scope){if(lang.isFunction(callback)){callback.apply(scope,params);}else{if(lang.isObject(callback)){scope=callback.scope||scope||window;var callbackFunc=callback.success;if(error){callbackFunc=callback.failure;}if(callbackFunc){callbackFunc.apply(scope,params.concat([callback.argument]));}}}},parseString:function(oData){if(!lang.isValue(oData)){return null;}var string=oData+"";if(lang.isString(string)){return string;}else{return null;}},parseNumber:function(oData){if(!lang.isValue(oData)||(oData==="")){return null;}var number=oData*1;if(lang.isNumber(number)){return number;}else{return null;}},convertNumber:function(oData){return DS.parseNumber(oData);},parseDate:function(oData){var date=null;if(lang.isValue(oData)&&!(oData instanceof Date)){date=new Date(oData);}else{return oData;}if(date instanceof Date){return date;}else{return null;}},convertDate:function(oData){return DS.parseDate(oData);}});DS.Parser={string:DS.parseString,number:DS.parseNumber,date:DS.parseDate};DS.prototype={_sName:null,_aCache:null,_oQueue:null,_aIntervals:null,maxCacheEntries:0,liveData:null,dataType:DS.TYPE_UNKNOWN,responseType:DS.TYPE_UNKNOWN,responseSchema:null,useXPath:false,cloneBeforeCaching:false,toString:function(){return this._sName;},getCachedResponse:function(oRequest,oCallback,oCaller){var aCache=this._aCache;if(this.maxCacheEntries>0){if(!aCache){this._aCache=[];}else{var nCacheLength=aCache.length;if(nCacheLength>0){var oResponse=null;this.fireEvent("cacheRequestEvent",{request:oRequest,callback:oCallback,caller:oCaller});for(var i=nCacheLength-1;i>=0;i--){var oCacheElem=aCache[i];if(this.isCacheHit(oRequest,oCacheElem.request)){oResponse=oCacheElem.response;this.fireEvent("cacheResponseEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller});if(i<nCacheLength-1){aCache.splice(i,1);this.addToCache(oRequest,oResponse);}oResponse.cached=true;break;}}return oResponse;}}}else{if(aCache){this._aCache=null;}}return null;},isCacheHit:function(oRequest,oCachedRequest){return(oRequest===oCachedRequest);},addToCache:function(oRequest,oResponse){var aCache=this._aCache;if(!aCache){return;}while(aCache.length>=this.maxCacheEntries){aCache.shift();}oResponse=(this.cloneBeforeCaching)?DS._cloneObject(oResponse):oResponse;var oCacheElem={request:oRequest,response:oResponse};aCache[aCache.length]=oCacheElem;this.fireEvent("responseCacheEvent",{request:oRequest,response:oResponse});},flushCache:function(){if(this._aCache){this._aCache=[];this.fireEvent("cacheFlushEvent");}},setInterval:function(nMsec,oRequest,oCallback,oCaller){if(lang.isNumber(nMsec)&&(nMsec>=0)){var oSelf=this;var nId=setInterval(function(){oSelf.makeConnection(oRequest,oCallback,oCaller);},nMsec);this._aIntervals.push(nId);return nId;}else{}},clearInterval:function(nId){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){if(tracker[i]===nId){tracker.splice(i,1);clearInterval(nId);}}},clearAllIntervals:function(){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){clearInterval(tracker[i]);}tracker=[];},sendRequest:function(oRequest,oCallback,oCaller){var oCachedResponse=this.getCachedResponse(oRequest,oCallback,oCaller);if(oCachedResponse){DS.issueCallback(oCallback,[oRequest,oCachedResponse],false,oCaller);return null;}return this.makeConnection(oRequest,oCallback,oCaller);},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=this.liveData;this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;},handleResponse:function(oRequest,oRawResponse,oCallback,oCaller,tId){this.fireEvent("responseEvent",{tId:tId,request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller});
+var xhr=(this.dataType==DS.TYPE_XHR)?true:false;var oParsedResponse=null;var oFullResponse=oRawResponse;if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oRawResponse&&oRawResponse.getResponseHeader)?oRawResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}else{if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse&&oRawResponse.nodeType&&(oRawResponse.nodeType===9||oRawResponse.nodeType===1||oRawResponse.nodeType===11)){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}}switch(this.responseType){case DS.TYPE_JSARRAY:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var arrayEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,arrayEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e1){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseArrayData(oRequest,oFullResponse);break;case DS.TYPE_JSON:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var objEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,objEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseJSONData(oRequest,oFullResponse);break;case DS.TYPE_HTMLTABLE:if(xhr&&oRawResponse.responseText){var el=document.createElement("div");el.innerHTML=oRawResponse.responseText;oFullResponse=el.getElementsByTagName("table")[0];}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseHTMLTableData(oRequest,oFullResponse);break;case DS.TYPE_XML:if(xhr&&oRawResponse.responseXML){oFullResponse=oRawResponse.responseXML;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseXMLData(oRequest,oFullResponse);break;case DS.TYPE_TEXT:if(xhr&&lang.isString(oRawResponse.responseText)){oFullResponse=oRawResponse.responseText;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseTextData(oRequest,oFullResponse);break;default:oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseData(oRequest,oFullResponse);break;}oParsedResponse=oParsedResponse||{};if(!oParsedResponse.results){oParsedResponse.results=[];}if(!oParsedResponse.meta){oParsedResponse.meta={};}if(!oParsedResponse.error){oParsedResponse=this.doBeforeCallback(oRequest,oFullResponse,oParsedResponse,oCallback);this.fireEvent("responseParseEvent",{request:oRequest,response:oParsedResponse,callback:oCallback,caller:oCaller});this.addToCache(oRequest,oParsedResponse);}else{oParsedResponse.error=true;this.fireEvent("dataErrorEvent",{request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});}oParsedResponse.tId=tId;DS.issueCallback(oCallback,[oRequest,oParsedResponse],oParsedResponse.error,oCaller);},doBeforeParseData:function(oRequest,oFullResponse,oCallback){return oFullResponse;},doBeforeCallback:function(oRequest,oFullResponse,oParsedResponse,oCallback){return oParsedResponse;},parseData:function(oRequest,oFullResponse){if(lang.isValue(oFullResponse)){var oParsedResponse={results:oFullResponse,meta:{}};return oParsedResponse;}return null;},parseArrayData:function(oRequest,oFullResponse){if(lang.isArray(oFullResponse)){var results=[],i,j,rec,field,data;if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(i=fields.length-1;i>=0;--i){if(typeof fields[i]!=="object"){fields[i]={key:fields[i]};}}var parsers={},p;for(i=fields.length-1;i>=0;--i){p=(typeof fields[i].parser==="function"?fields[i].parser:DS.Parser[fields[i].parser+""])||fields[i].converter;if(p){parsers[fields[i].key]=p;}}var arrType=lang.isArray(oFullResponse[0]);for(i=oFullResponse.length-1;i>-1;i--){var oResult={};rec=oFullResponse[i];if(typeof rec==="object"){for(j=fields.length-1;j>-1;j--){field=fields[j];data=arrType?rec[j]:rec[field.key];if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;}}else{if(lang.isString(rec)){for(j=fields.length-1;j>-1;j--){field=fields[j];data=rec;if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;
+}}}results[i]=oResult;}}else{results=oFullResponse;}var oParsedResponse={results:results};return oParsedResponse;}return null;},parseTextData:function(oRequest,oFullResponse){if(lang.isString(oFullResponse)){if(lang.isString(this.responseSchema.recordDelim)&&lang.isString(this.responseSchema.fieldDelim)){var oParsedResponse={results:[]};var recDelim=this.responseSchema.recordDelim;var fieldDelim=this.responseSchema.fieldDelim;if(oFullResponse.length>0){var newLength=oFullResponse.length-recDelim.length;if(oFullResponse.substr(newLength)==recDelim){oFullResponse=oFullResponse.substr(0,newLength);}if(oFullResponse.length>0){var recordsarray=oFullResponse.split(recDelim);for(var i=0,len=recordsarray.length,recIdx=0;i<len;++i){var bError=false,sRecord=recordsarray[i];if(lang.isString(sRecord)&&(sRecord.length>0)){var fielddataarray=recordsarray[i].split(fieldDelim);var oResult={};if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(var j=fields.length-1;j>-1;j--){try{var data=fielddataarray[j];if(lang.isString(data)){if(data.charAt(0)=='"'){data=data.substr(1);}if(data.charAt(data.length-1)=='"'){data=data.substr(0,data.length-1);}var field=fields[j];var key=(lang.isValue(field.key))?field.key:field;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}else{bError=true;}}catch(e){bError=true;}}}else{oResult=fielddataarray;}if(!bError){oParsedResponse.results[recIdx++]=oResult;}}}}}return oParsedResponse;}}return null;},parseXMLResult:function(result){var oResult={},schema=this.responseSchema;try{for(var m=schema.fields.length-1;m>=0;m--){var field=schema.fields[m];var key=(lang.isValue(field.key))?field.key:field;var data=null;if(this.useXPath){data=YAHOO.util.DataSource._getLocationValue(field,result);}else{var xmlAttr=result.attributes.getNamedItem(key);if(xmlAttr){data=xmlAttr.value;}else{var xmlNode=result.getElementsByTagName(key);if(xmlNode&&xmlNode.item(0)){var item=xmlNode.item(0);data=(item)?((item.text)?item.text:(item.textContent)?item.textContent:null):null;if(!data){var datapieces=[];for(var j=0,len=item.childNodes.length;j<len;j++){if(item.childNodes[j].nodeValue){datapieces[datapieces.length]=item.childNodes[j].nodeValue;}}if(datapieces.length>0){data=datapieces.join("");}}}}}if(data===null){data="";}if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}}catch(e){}return oResult;},parseXMLData:function(oRequest,oFullResponse){var bError=false,schema=this.responseSchema,oParsedResponse={meta:{}},xmlList=null,metaNode=schema.metaNode,metaLocators=schema.metaFields||{},i,k,loc,v;try{if(this.useXPath){for(k in metaLocators){oParsedResponse.meta[k]=YAHOO.util.DataSource._getLocationValue(metaLocators[k],oFullResponse);}}else{metaNode=metaNode?oFullResponse.getElementsByTagName(metaNode)[0]:oFullResponse;if(metaNode){for(k in metaLocators){if(lang.hasOwnProperty(metaLocators,k)){loc=metaLocators[k];v=metaNode.getElementsByTagName(loc)[0];if(v){v=v.firstChild.nodeValue;}else{v=metaNode.attributes.getNamedItem(loc);if(v){v=v.value;}}if(lang.isValue(v)){oParsedResponse.meta[k]=v;}}}}}xmlList=(schema.resultNode)?oFullResponse.getElementsByTagName(schema.resultNode):null;}catch(e){}if(!xmlList||!lang.isArray(schema.fields)){bError=true;}else{oParsedResponse.results=[];for(i=xmlList.length-1;i>=0;--i){var oResult=this.parseXMLResult(xmlList.item(i));oParsedResponse.results[i]=oResult;}}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;},parseJSONData:function(oRequest,oFullResponse){var oParsedResponse={results:[],meta:{}};if(lang.isObject(oFullResponse)&&this.responseSchema.resultsList){var schema=this.responseSchema,fields=schema.fields,resultsList=oFullResponse,results=[],metaFields=schema.metaFields||{},fieldParsers=[],fieldPaths=[],simpleFields=[],bError=false,i,len,j,v,key,parser,path;var buildPath=function(needle){var path=null,keys=[],i=0;if(needle){needle=needle.replace(/\[(['"])(.*?)\1\]/g,function(x,$1,$2){keys[i]=$2;return".@"+(i++);}).replace(/\[(\d+)\]/g,function(x,$1){keys[i]=parseInt($1,10)|0;return".@"+(i++);}).replace(/^\./,"");if(!/[^\w\.\$@]/.test(needle)){path=needle.split(".");for(i=path.length-1;i>=0;--i){if(path[i].charAt(0)==="@"){path[i]=keys[parseInt(path[i].substr(1),10)];}}}else{}}return path;};var walkPath=function(path,origin){var v=origin,i=0,len=path.length;for(;i<len&&v;++i){v=v[path[i]];}return v;};path=buildPath(schema.resultsList);if(path){resultsList=walkPath(path,oFullResponse);if(resultsList===undefined){bError=true;}}else{bError=true;}if(!resultsList){resultsList=[];}if(!lang.isArray(resultsList)){resultsList=[resultsList];}if(!bError){if(schema.fields){var field;for(i=0,len=fields.length;i<len;i++){field=fields[i];key=field.key||field;parser=((typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""])||field.converter;path=buildPath(key);if(parser){fieldParsers[fieldParsers.length]={key:key,parser:parser};}if(path){if(path.length>1){fieldPaths[fieldPaths.length]={key:key,path:path};}else{simpleFields[simpleFields.length]={key:key,path:path[0]};}}else{}}for(i=resultsList.length-1;i>=0;--i){var r=resultsList[i],rec={};if(r){for(j=simpleFields.length-1;j>=0;--j){rec[simpleFields[j].key]=(r[simpleFields[j].path]!==undefined)?r[simpleFields[j].path]:r[j];}for(j=fieldPaths.length-1;j>=0;--j){rec[fieldPaths[j].key]=walkPath(fieldPaths[j].path,r);}for(j=fieldParsers.length-1;j>=0;--j){var p=fieldParsers[j].key;rec[p]=fieldParsers[j].parser.call(this,rec[p]);if(rec[p]===undefined){rec[p]=null;}}}results[i]=rec;}}else{results=resultsList;}for(key in metaFields){if(lang.hasOwnProperty(metaFields,key)){path=buildPath(metaFields[key]);
+if(path){v=walkPath(path,oFullResponse);oParsedResponse.meta[key]=v;}}}}else{oParsedResponse.error=true;}oParsedResponse.results=results;}else{oParsedResponse.error=true;}return oParsedResponse;},parseHTMLTableData:function(oRequest,oFullResponse){var bError=false;var elTable=oFullResponse;var fields=this.responseSchema.fields;var oParsedResponse={results:[]};if(lang.isArray(fields)){for(var i=0;i<elTable.tBodies.length;i++){var elTbody=elTable.tBodies[i];for(var j=elTbody.rows.length-1;j>-1;j--){var elRow=elTbody.rows[j];var oResult={};for(var k=fields.length-1;k>-1;k--){var field=fields[k];var key=(lang.isValue(field.key))?field.key:field;var data=elRow.cells[k].innerHTML;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}oParsedResponse.results[j]=oResult;}}}else{bError=true;}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;}};lang.augmentProto(DS,util.EventProvider);util.LocalDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_LOCAL;if(oLiveData){if(YAHOO.lang.isArray(oLiveData)){this.responseType=DS.TYPE_JSARRAY;}else{if(oLiveData.nodeType&&oLiveData.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oLiveData.nodeName&&(oLiveData.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;oLiveData=oLiveData.cloneNode(true);}else{if(YAHOO.lang.isString(oLiveData)){this.responseType=DS.TYPE_TEXT;}else{if(YAHOO.lang.isObject(oLiveData)){this.responseType=DS.TYPE_JSON;}}}}}}else{oLiveData=[];this.responseType=DS.TYPE_JSARRAY;}util.LocalDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.LocalDataSource,DS);lang.augmentObject(util.LocalDataSource,DS);util.FunctionDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_JSFUNCTION;oLiveData=oLiveData||function(){};util.FunctionDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.FunctionDataSource,DS,{scope:null,makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=(this.scope)?this.liveData.call(this.scope,oRequest,this,oCallback):this.liveData(oRequest,oCallback);if(this.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse&&oRawResponse.nodeType&&oRawResponse.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;}});lang.augmentObject(util.FunctionDataSource,DS);util.ScriptNodeDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_SCRIPTNODE;oLiveData=oLiveData||"";util.ScriptNodeDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.ScriptNodeDataSource,DS,{getUtility:util.Get,asyncMode:"allowAll",scriptCallbackParam:"callback",generateRequestCallback:function(id){return"&"+this.scriptCallbackParam+"=YAHOO.util.ScriptNodeDataSource.callbacks["+id+"]";},doBeforeGetScriptNode:function(sUri){return sUri;},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});if(util.ScriptNodeDataSource._nPending===0){util.ScriptNodeDataSource.callbacks=[];util.ScriptNodeDataSource._nId=0;}var id=util.ScriptNodeDataSource._nId;util.ScriptNodeDataSource._nId++;var oSelf=this;util.ScriptNodeDataSource.callbacks[id]=function(oRawResponse){if((oSelf.asyncMode!=="ignoreStaleResponses")||(id===util.ScriptNodeDataSource.callbacks.length-1)){if(oSelf.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){oSelf.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse.nodeType&&oRawResponse.nodeType==9){oSelf.responseType=DS.TYPE_XML;}else{if(oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){oSelf.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){oSelf.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){oSelf.responseType=DS.TYPE_TEXT;}}}}}}oSelf.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);}else{}delete util.ScriptNodeDataSource.callbacks[id];};util.ScriptNodeDataSource._nPending++;var sUri=this.liveData+oRequest+this.generateRequestCallback(id);sUri=this.doBeforeGetScriptNode(sUri);this.getUtility.script(sUri,{autopurge:true,onsuccess:util.ScriptNodeDataSource._bumpPendingDown,onfail:util.ScriptNodeDataSource._bumpPendingDown});return tId;}});lang.augmentObject(util.ScriptNodeDataSource,DS);lang.augmentObject(util.ScriptNodeDataSource,{_nId:0,_nPending:0,callbacks:[]});util.XHRDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_XHR;this.connMgr=this.connMgr||util.Connect;oLiveData=oLiveData||"";util.XHRDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.XHRDataSource,DS,{connMgr:null,connXhrMode:"allowAll",connMethodPost:false,connTimeout:0,makeConnection:function(oRequest,oCallback,oCaller){var oRawResponse=null;var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oSelf=this;var oConnMgr=this.connMgr;var oQueue=this._oQueue;var _xhrSuccess=function(oResponse){if(oResponse&&(this.connXhrMode=="ignoreStaleResponses")&&(oResponse.tId!=oQueue.conn.tId)){return null;}else{if(!oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:null,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);return null;
+}else{if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oResponse.getResponseHeader)?oResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}}this.handleResponse(oRequest,oResponse,oCallback,oCaller,tId);}}};var _xhrFailure=function(oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATAINVALID});if(lang.isString(this.liveData)&&lang.isString(oRequest)&&(this.liveData.lastIndexOf("?")!==this.liveData.length-1)&&(oRequest.indexOf("?")!==0)){}oResponse=oResponse||{};oResponse.error=true;DS.issueCallback(oCallback,[oRequest,oResponse],true,oCaller);return null;};var _xhrCallback={success:_xhrSuccess,failure:_xhrFailure,scope:this};if(lang.isNumber(this.connTimeout)){_xhrCallback.timeout=this.connTimeout;}if(this.connXhrMode=="cancelStaleRequests"){if(oQueue.conn){if(oConnMgr.abort){oConnMgr.abort(oQueue.conn);oQueue.conn=null;}else{}}}if(oConnMgr&&oConnMgr.asyncRequest){var sLiveData=this.liveData;var isPost=this.connMethodPost;var sMethod=(isPost)?"POST":"GET";var sUri=(isPost||!lang.isValue(oRequest))?sLiveData:sLiveData+oRequest;var sRequest=(isPost)?oRequest:null;if(this.connXhrMode!="queueRequests"){oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}else{if(oQueue.conn){var allRequests=oQueue.requests;allRequests.push({request:oRequest,callback:_xhrCallback});if(!oQueue.interval){oQueue.interval=setInterval(function(){if(oConnMgr.isCallInProgress(oQueue.conn)){return;}else{if(allRequests.length>0){sUri=(isPost||!lang.isValue(allRequests[0].request))?sLiveData:sLiveData+allRequests[0].request;sRequest=(isPost)?allRequests[0].request:null;oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,allRequests[0].callback,sRequest);allRequests.shift();}else{clearInterval(oQueue.interval);oQueue.interval=null;}}},50);}}else{oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}}}else{DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);}return tId;}});lang.augmentObject(util.XHRDataSource,DS);util.DataSource=function(oLiveData,oConfigs){oConfigs=oConfigs||{};var dataType=oConfigs.dataType;if(dataType){if(dataType==DS.TYPE_LOCAL){return new util.LocalDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_XHR){return new util.XHRDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_SCRIPTNODE){return new util.ScriptNodeDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_JSFUNCTION){return new util.FunctionDataSource(oLiveData,oConfigs);}}}}}if(YAHOO.lang.isString(oLiveData)){return new util.XHRDataSource(oLiveData,oConfigs);}else{if(YAHOO.lang.isFunction(oLiveData)){return new util.FunctionDataSource(oLiveData,oConfigs);}else{return new util.LocalDataSource(oLiveData,oConfigs);}}};lang.augmentObject(util.DataSource,DS);})();YAHOO.util.Number={format:function(e,k){if(e===""||e===null||!isFinite(e)){return"";}e=+e;k=YAHOO.lang.merge(YAHOO.util.Number.format.defaults,(k||{}));var j=e+"",l=Math.abs(e),b=k.decimalPlaces||0,r=k.thousandsSeparator,f=k.negativeFormat||("-"+k.format),q,p,g,h;if(f.indexOf("#")>-1){f=f.replace(/#/,k.format);}if(b<0){q=l-(l%1)+"";g=q.length+b;if(g>0){q=Number("."+q).toFixed(g).slice(2)+new Array(q.length-g+1).join("0");}else{q="0";}}else{var a=l+"";if(b>0||a.indexOf(".")>0){var d=Math.pow(10,b);q=Math.round(l*d)/d+"";var c=q.indexOf("."),m,o;if(c<0){m=b;o=(Math.pow(10,m)+"").substring(1);if(b>0){q=q+"."+o;}}else{m=b-(q.length-c-1);o=(Math.pow(10,m)+"").substring(1);q=q+o;}}else{q=l.toFixed(b)+"";}}p=q.split(/\D/);if(l>=1000){g=p[0].length%3||3;p[0]=p[0].slice(0,g)+p[0].slice(g).replace(/(\d{3})/g,r+"$1");}return YAHOO.util.Number.format._applyFormat((e<0?f:k.format),p.join(k.decimalSeparator),k);}};YAHOO.util.Number.format.defaults={format:"{prefix}{number}{suffix}",negativeFormat:null,decimalSeparator:".",decimalPlaces:null,thousandsSeparator:""};YAHOO.util.Number.format._applyFormat=function(a,b,c){return a.replace(/\{(\w+)\}/g,function(d,e){return e==="number"?b:e in c?c[e]:"";});};(function(){var a=function(c,e,d){if(typeof d==="undefined"){d=10;}for(;parseInt(c,10)<d&&d>1;d/=10){c=e.toString()+c;}return c.toString();};var b={formats:{a:function(e,c){return c.a[e.getDay()];},A:function(e,c){return c.A[e.getDay()];},b:function(e,c){return c.b[e.getMonth()];},B:function(e,c){return c.B[e.getMonth()];},C:function(c){return a(parseInt(c.getFullYear()/100,10),0);},d:["getDate","0"],e:["getDate"," "],g:function(c){return a(parseInt(b.formats.G(c)%100,10),0);},G:function(f){var g=f.getFullYear();var e=parseInt(b.formats.V(f),10);var c=parseInt(b.formats.W(f),10);if(c>e){g++;}else{if(c===0&&e>=52){g--;}}return g;},H:["getHours","0"],I:function(e){var c=e.getHours()%12;return a(c===0?12:c,0);},j:function(h){var g=new Date(""+h.getFullYear()+"/1/1 GMT");var e=new Date(""+h.getFullYear()+"/"+(h.getMonth()+1)+"/"+h.getDate()+" GMT");var c=e-g;var f=parseInt(c/60000/60/24,10)+1;return a(f,0,100);},k:["getHours"," "],l:function(e){var c=e.getHours()%12;return a(c===0?12:c," ");},m:function(c){return a(c.getMonth()+1,0);},M:["getMinutes","0"],p:function(e,c){return c.p[e.getHours()>=12?1:0];},P:function(e,c){return c.P[e.getHours()>=12?1:0];},s:function(e,c){return parseInt(e.getTime()/1000,10);},S:["getSeconds","0"],u:function(c){var e=c.getDay();return e===0?7:e;},U:function(g){var c=parseInt(b.formats.j(g),10);var f=6-g.getDay();var e=parseInt((c+f)/7,10);return a(e,0);},V:function(g){var f=parseInt(b.formats.W(g),10);var c=(new Date(""+g.getFullYear()+"/1/1")).getDay();var e=f+(c>4||c<=1?0:1);if(e===53&&(new Date(""+g.getFullYear()+"/12/31")).getDay()<4){e=1;}else{if(e===0){e=b.formats.V(new Date(""+(g.getFullYear()-1)+"/12/31"));}}return a(e,0);},w:"getDay",W:function(g){var c=parseInt(b.formats.j(g),10);var f=7-b.formats.u(g);var e=parseInt((c+f)/7,10);
+return a(e,0,10);},y:function(c){return a(c.getFullYear()%100,0);},Y:"getFullYear",z:function(f){var e=f.getTimezoneOffset();var c=a(parseInt(Math.abs(e/60),10),0);var g=a(Math.abs(e%60),0);return(e>0?"-":"+")+c+g;},Z:function(c){var e=c.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/,"$2").replace(/[a-z ]/g,"");if(e.length>4){e=b.formats.z(c);}return e;},"%":function(c){return"%";}},aggregates:{c:"locale",D:"%m/%d/%y",F:"%Y-%m-%d",h:"%b",n:"\n",r:"locale",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"},format:function(g,f,d){f=f||{};if(!(g instanceof Date)){return YAHOO.lang.isValue(g)?g:"";}var h=f.format||"%m/%d/%Y";if(h==="YYYY/MM/DD"){h="%Y/%m/%d";}else{if(h==="DD/MM/YYYY"){h="%d/%m/%Y";}else{if(h==="MM/DD/YYYY"){h="%m/%d/%Y";}}}d=d||"en";if(!(d in YAHOO.util.DateLocale)){if(d.replace(/-[a-zA-Z]+$/,"") in YAHOO.util.DateLocale){d=d.replace(/-[a-zA-Z]+$/,"");}else{d="en";}}var j=YAHOO.util.DateLocale[d];var c=function(l,k){var m=b.aggregates[k];return(m==="locale"?j[k]:m);};var e=function(l,k){var m=b.formats[k];if(typeof m==="string"){return g[m]();}else{if(typeof m==="function"){return m.call(g,g,j);}else{if(typeof m==="object"&&typeof m[0]==="string"){return a(g[m[0]](),m[1]);}else{return k;}}}};while(h.match(/%[cDFhnrRtTxX]/)){h=h.replace(/%([cDFhnrRtTxX])/g,c);}var i=h.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g,e);c=e=undefined;return i;}};YAHOO.namespace("YAHOO.util");YAHOO.util.Date=b;YAHOO.util.DateLocale={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],r:"%I:%M:%S %p",x:"%d/%m/%y",X:"%T"};YAHOO.util.DateLocale["en"]=YAHOO.lang.merge(YAHOO.util.DateLocale,{});YAHOO.util.DateLocale["en-US"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{c:"%a %d %b %Y %I:%M:%S %p %Z",x:"%m/%d/%Y",X:"%I:%M:%S %p"});YAHOO.util.DateLocale["en-GB"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{r:"%l:%M:%S %P %Z"});YAHOO.util.DateLocale["en-AU"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"]);})();YAHOO.register("datasource",YAHOO.util.DataSource,{version:"2.9.0",build:"2800"});/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.util.Chain=function(){this.q=[].slice.call(arguments);this.createEvent("end");};YAHOO.util.Chain.prototype={id:0,run:function(){var g=this.q[0],d;if(!g){this.fireEvent("end");return this;}else{if(this.id){return this;}}d=g.method||g;if(typeof d==="function"){var f=g.scope||{},b=g.argument||[],a=g.timeout||0,e=this;if(!(b instanceof Array)){b=[b];}if(a<0){this.id=a;if(g.until){for(;!g.until();){d.apply(f,b);}}else{if(g.iterations){for(;g.iterations-->0;){d.apply(f,b);}}else{d.apply(f,b);}}this.q.shift();this.id=0;return this.run();}else{if(g.until){if(g.until()){this.q.shift();return this.run();}}else{if(!g.iterations||!--g.iterations){this.q.shift();}}this.id=setTimeout(function(){d.apply(f,b);if(e.id){e.id=0;e.run();}},a);}}return this;},add:function(a){this.q.push(a);return this;},pause:function(){if(this.id>0){clearTimeout(this.id);}this.id=0;return this;},stop:function(){this.pause();this.q=[];return this;}};YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);(function(){var a=YAHOO.util.Event,c=YAHOO.lang,b=[],d=function(h,e,f){var g;if(!h||h===f){g=false;}else{g=YAHOO.util.Selector.test(h,e)?h:d(h.parentNode,e,f);}return g;};c.augmentObject(a,{_createDelegate:function(f,e,g,h){return function(i){var j=this,n=a.getTarget(i),l=e,p=(j.nodeType===9),q,k,o,m;if(c.isFunction(e)){q=e(n);}else{if(c.isString(e)){if(!p){o=j.id;if(!o){o=a.generateId(j);}m=("#"+o+" ");l=(m+e).replace(/,/gi,(","+m));}if(YAHOO.util.Selector.test(n,l)){q=n;}else{if(YAHOO.util.Selector.test(n,((l.replace(/,/gi," *,"))+" *"))){q=d(n,l,j);}}}}if(q){k=q;if(h){if(h===true){k=g;}else{k=h;}}return f.call(k,i,q,j,g);}};},delegate:function(f,j,l,g,h,i){var e=j,k,m;if(c.isString(g)&&!YAHOO.util.Selector){return false;}if(j=="mouseenter"||j=="mouseleave"){if(!a._createMouseDelegate){return false;}e=a._getType(j);k=a._createMouseDelegate(l,h,i);m=a._createDelegate(function(p,o,n){return k.call(o,p,n);},g,h,i);}else{m=a._createDelegate(l,g,h,i);}b.push([f,e,l,m]);return a.on(f,e,m);},removeDelegate:function(f,j,i){var k=j,h=false,g,e;if(j=="mouseenter"||j=="mouseleave"){k=a._getType(j);}g=a._getCacheIndex(b,f,k,i);if(g>=0){e=b[g];}if(f&&e){h=a.removeListener(e[0],e[1],e[3]);if(h){delete b[g][2];delete b[g][3];b.splice(g,1);}}return h;}});}());(function(){var b=YAHOO.util.Event,g=YAHOO.lang,e=b.addListener,f=b.removeListener,c=b.getListeners,d=[],h={mouseenter:"mouseover",mouseleave:"mouseout"},a=function(n,m,l){var j=b._getCacheIndex(d,n,m,l),i,k;if(j>=0){i=d[j];}if(n&&i){k=f.call(b,i[0],m,i[3]);if(k){delete d[j][2];delete d[j][3];d.splice(j,1);}}return k;};g.augmentObject(b._specialTypes,h);g.augmentObject(b,{_createMouseDelegate:function(i,j,k){return function(q,m){var p=this,l=b.getRelatedTarget(q),o,n;if(p!=l&&!YAHOO.util.Dom.isAncestor(p,l)){o=p;if(k){if(k===true){o=j;}else{o=k;}}n=[q,j];if(m){n.splice(1,0,p,m);}return i.apply(o,n);}};},addListener:function(m,l,k,n,o){var i,j;if(h[l]){i=b._createMouseDelegate(k,n,o);i.mouseDelegate=true;d.push([m,l,k,i]);j=e.call(b,m,l,i);}else{j=e.apply(b,arguments);}return j;},removeListener:function(l,k,j){var i;if(h[k]){i=a.apply(b,arguments);}else{i=f.apply(b,arguments);}return i;},getListeners:function(p,o){var n=[],r,m=(o==="mouseover"||o==="mouseout"),q,k,j;if(o&&(m||h[o])){r=c.call(b,p,this._getType(o));if(r){for(k=r.length-1;k>-1;k--){j=r[k];q=j.fn.mouseDelegate;if((h[o]&&q)||(m&&!q)){n.push(j);}}}}else{n=c.apply(b,arguments);}return(n&&n.length)?n:null;}},true);b.on=b.addListener;}());YAHOO.register("event-mouseenter",YAHOO.util.Event,{version:"2.9.0",build:"2800"});var Y=YAHOO,Y_DOM=YAHOO.util.Dom,EMPTY_ARRAY=[],Y_UA=Y.env.ua,Y_Lang=Y.lang,Y_DOC=document,Y_DOCUMENT_ELEMENT=Y_DOC.documentElement,Y_DOM_inDoc=Y_DOM.inDocument,Y_mix=Y_Lang.augmentObject,Y_guid=Y_DOM.generateId,Y_getDoc=function(a){var b=Y_DOC;if(a){b=(a.nodeType===9)?a:a.ownerDocument||a.document||Y_DOC;}return b;},Y_Array=function(g,d){var c,b,h=d||0;try{return Array.prototype.slice.call(g,h);}catch(f){b=[];c=g.length;for(;h<c;h++){b.push(g[h]);}return b;}},Y_DOM_allById=function(f,a){a=a||Y_DOC;var b=[],c=[],d,e;if(a.querySelectorAll){c=a.querySelectorAll('[id="'+f+'"]');}else{if(a.all){b=a.all(f);if(b){if(b.nodeName){if(b.id===f){c.push(b);b=EMPTY_ARRAY;}else{b=[b];}}if(b.length){for(d=0;e=b[d++];){if(e.id===f||(e.attributes&&e.attributes.id&&e.attributes.id.value===f)){c.push(e);}}}}}else{c=[Y_getDoc(a).getElementById(f)];}}return c;};var COMPARE_DOCUMENT_POSITION="compareDocumentPosition",OWNER_DOCUMENT="ownerDocument",Selector={_foundCache:[],useNative:true,_compare:("sourceIndex" in Y_DOCUMENT_ELEMENT)?function(f,e){var d=f.sourceIndex,c=e.sourceIndex;if(d===c){return 0;}else{if(d>c){return 1;}}return -1;}:(Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION]?function(b,a){if(b[COMPARE_DOCUMENT_POSITION](a)&4){return -1;}else{return 1;}}:function(e,d){var c,a,b;if(e&&d){c=e[OWNER_DOCUMENT].createRange();c.setStart(e,0);a=d[OWNER_DOCUMENT].createRange();a.setStart(d,0);b=c.compareBoundaryPoints(1,a);}return b;}),_sort:function(a){if(a){a=Y_Array(a,0,true);if(a.sort){a.sort(Selector._compare);}}return a;},_deDupe:function(a){var b=[],c,d;for(c=0;(d=a[c++]);){if(!d._found){b[b.length]=d;d._found=true;}}for(c=0;(d=b[c++]);){d._found=null;d.removeAttribute("_found");}return b;},query:function(b,j,k,a){if(typeof j=="string"){j=Y_DOM.get(j);if(!j){return(k)?null:[];}}else{j=j||Y_DOC;}var f=[],c=(Selector.useNative&&Y_DOC.querySelector&&!a),e=[[b,j]],g,l,d,h=(c)?Selector._nativeQuery:Selector._bruteQuery;if(b&&h){if(!a&&(!c||j.tagName)){e=Selector._splitQueries(b,j);}for(d=0;(g=e[d++]);){l=h(g[0],g[1],k);if(!k){l=Y_Array(l,0,true);}if(l){f=f.concat(l);}}if(e.length>1){f=Selector._sort(Selector._deDupe(f));}}Y.log("query: "+b+" returning: "+f.length,"info","Selector");return(k)?(f[0]||null):f;},_splitQueries:function(c,f){var b=c.split(","),d=[],g="",e,a;if(f){if(f.tagName){f.id=f.id||Y_guid();g='[id="'+f.id+'"] ';}for(e=0,a=b.length;e<a;++e){c=g+b[e];d.push([c,f]);}}return d;},_nativeQuery:function(a,b,c){if(Y_UA.webkit&&a.indexOf(":checked")>-1&&(Selector.pseudos&&Selector.pseudos.checked)){return Selector.query(a,b,c,true);
+}try{return b["querySelector"+(c?"":"All")](a);}catch(d){return Selector.query(a,b,c,true);}},filter:function(b,a){var c=[],d,e;if(b&&a){for(d=0;(e=b[d++]);){if(Selector.test(e,a)){c[c.length]=e;}}}else{Y.log("invalid filter input (nodes: "+b+", selector: "+a+")","warn","Selector");}return c;},test:function(c,d,k){var g=false,b=d.split(","),a=false,l,o,h,n,f,e,m;if(c&&c.tagName){if(!k&&!Y_DOM_inDoc(c)){l=c.parentNode;if(l){k=l;}else{n=c[OWNER_DOCUMENT].createDocumentFragment();n.appendChild(c);k=n;a=true;}}k=k||c[OWNER_DOCUMENT];if(!c.id){c.id=Y_guid();}for(f=0;(m=b[f++]);){m+='[id="'+c.id+'"]';h=Selector.query(m,k);for(e=0;o=h[e++];){if(o===c){g=true;break;}}if(g){break;}}if(a){n.removeChild(c);}}return g;}};YAHOO.util.Selector=Selector;var PARENT_NODE="parentNode",TAG_NAME="tagName",ATTRIBUTES="attributes",COMBINATOR="combinator",PSEUDOS="pseudos",SelectorCSS2={_reRegExpTokens:/([\^\$\?\[\]\*\+\-\.\(\)\|\\])/,SORT_RESULTS:true,_children:function(e,a){var b=e.children,d,c=[],f,g;if(e.children&&a&&e.children.tags){c=e.children.tags(a);}else{if((!b&&e[TAG_NAME])||(b&&a)){f=b||e.childNodes;b=[];for(d=0;(g=f[d++]);){if(g.tagName){if(!a||a===g.tagName){b.push(g);}}}}}return b||[];},_re:{attr:/(\[[^\]]*\])/g,esc:/\\[:\[\]\(\)#\.\'\>+~"]/gi,pseudos:/(\([^\)]*\))/g},shorthand:{"\\#(-?[_a-z]+[-\\w\\uE000]*)":"[id=$1]","\\.(-?[_a-z]+[-\\w\\uE000]*)":"[className~=$1]"},operators:{"":function(b,a){return !!b.getAttribute(a);},"~=":"(?:^|\\s+){val}(?:\\s+|$)","|=":"^{val}(?:-|$)"},pseudos:{"first-child":function(a){return Selector._children(a[PARENT_NODE])[0]===a;}},_bruteQuery:function(f,j,l){var g=[],a=[],i=Selector._tokenize(f),e=i[i.length-1],k=Y_getDoc(j),c,b,h,d;if(e){b=e.id;h=e.className;d=e.tagName||"*";if(j.getElementsByTagName){if(b&&(j.all||(j.nodeType===9||Y_DOM_inDoc(j)))){a=Y_DOM_allById(b,j);}else{if(h){a=j.getElementsByClassName(h);}else{a=j.getElementsByTagName(d);}}}else{c=j.firstChild;while(c){if(c.tagName){a.push(c);}c=c.nextSilbing||c.firstChild;}}if(a.length){g=Selector._filterNodes(a,i,l);}}return g;},_filterNodes:function(l,f,h){var r=0,q,s=f.length,k=s-1,e=[],o=l[0],v=o,t=Selector.getters,d,p,c,g,a,m,b,u;for(r=0;(v=o=l[r++]);){k=s-1;g=null;testLoop:while(v&&v.tagName){c=f[k];b=c.tests;q=b.length;if(q&&!a){while((u=b[--q])){d=u[1];if(t[u[0]]){m=t[u[0]](v,u[0]);}else{m=v[u[0]];if(m===undefined&&v.getAttribute){m=v.getAttribute(u[0]);}}if((d==="="&&m!==u[2])||(typeof d!=="string"&&d.test&&!d.test(m))||(!d.test&&typeof d==="function"&&!d(v,u[0],u[2]))){if((v=v[g])){while(v&&(!v.tagName||(c.tagName&&c.tagName!==v.tagName))){v=v[g];}}continue testLoop;}}}k--;if(!a&&(p=c.combinator)){g=p.axis;v=v[g];while(v&&!v.tagName){v=v[g];}if(p.direct){g=null;}}else{e.push(o);if(h){return e;}break;}}}o=v=null;return e;},combinators:{" ":{axis:"parentNode"},">":{axis:"parentNode",direct:true},"+":{axis:"previousSibling",direct:true}},_parsers:[{name:ATTRIBUTES,re:/^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,fn:function(d,e){var c=d[2]||"",a=Selector.operators,b=(d[3])?d[3].replace(/\\/g,""):"",f;if((d[1]==="id"&&c==="=")||(d[1]==="className"&&Y_DOCUMENT_ELEMENT.getElementsByClassName&&(c==="~="||c==="="))){e.prefilter=d[1];d[3]=b;e[d[1]]=(d[1]==="id")?d[3]:b;}if(c in a){f=a[c];if(typeof f==="string"){d[3]=b.replace(Selector._reRegExpTokens,"\\$1");f=new RegExp(f.replace("{val}",d[3]));}d[2]=f;}if(!e.last||e.prefilter!==d[1]){return d.slice(1);}}},{name:TAG_NAME,re:/^((?:-?[_a-z]+[\w-]*)|\*)/i,fn:function(b,c){var a=b[1].toUpperCase();c.tagName=a;if(a!=="*"&&(!c.last||c.prefilter)){return[TAG_NAME,"=",a];}if(!c.prefilter){c.prefilter="tagName";}}},{name:COMBINATOR,re:/^\s*([>+~]|\s)\s*/,fn:function(a,b){}},{name:PSEUDOS,re:/^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,fn:function(a,b){var c=Selector[PSEUDOS][a[1]];if(c){if(a[2]){a[2]=a[2].replace(/\\/g,"");}return[a[2],c];}else{return false;}}}],_getToken:function(a){return{tagName:null,id:null,className:null,attributes:{},combinator:null,tests:[]};},_tokenize:function(c){c=c||"";c=Selector._replaceShorthand(Y_Lang.trim(c));var b=Selector._getToken(),h=c,g=[],j=false,e,f,d,a;outer:do{j=false;for(d=0;(a=Selector._parsers[d++]);){if((e=a.re.exec(c))){if(a.name!==COMBINATOR){b.selector=c;}c=c.replace(e[0],"");if(!c.length){b.last=true;}if(Selector._attrFilters[e[1]]){e[1]=Selector._attrFilters[e[1]];}f=a.fn(e,b);if(f===false){j=false;break outer;}else{if(f){b.tests.push(f);}}if(!c.length||a.name===COMBINATOR){g.push(b);b=Selector._getToken(b);if(a.name===COMBINATOR){b.combinator=Selector.combinators[e[1]];}}j=true;}}}while(j&&c.length);if(!j||c.length){Y.log("query: "+h+" contains unsupported token in: "+c,"warn","Selector");g=[];}return g;},_replaceShorthand:function(b){var d=Selector.shorthand,c=b.match(Selector._re.esc),e,h,g,f,a;if(c){b=b.replace(Selector._re.esc,"\uE000");}e=b.match(Selector._re.attr);h=b.match(Selector._re.pseudos);if(e){b=b.replace(Selector._re.attr,"\uE001");}if(h){b=b.replace(Selector._re.pseudos,"\uE002");}for(g in d){if(d.hasOwnProperty(g)){b=b.replace(new RegExp(g,"gi"),d[g]);}}if(e){for(f=0,a=e.length;f<a;++f){b=b.replace(/\uE001/,e[f]);}}if(h){for(f=0,a=h.length;f<a;++f){b=b.replace(/\uE002/,h[f]);}}b=b.replace(/\[/g,"\uE003");b=b.replace(/\]/g,"\uE004");b=b.replace(/\(/g,"\uE005");b=b.replace(/\)/g,"\uE006");if(c){for(f=0,a=c.length;f<a;++f){b=b.replace("\uE000",c[f]);}}return b;},_attrFilters:{"class":"className","for":"htmlFor"},getters:{href:function(b,a){return Y_DOM.getAttribute(b,a);}}};Y_mix(Selector,SelectorCSS2,true);Selector.getters.src=Selector.getters.rel=Selector.getters.href;if(Selector.useNative&&Y_DOC.querySelector){Selector.shorthand["\\.([^\\s\\\\(\\[:]*)"]="[class~=$1]";}Selector._reNth=/^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;Selector._getNth=function(d,o,q,h){Selector._reNth.test(o);var m=parseInt(RegExp.$1,10),c=RegExp.$2,j=RegExp.$3,k=parseInt(RegExp.$4,10)||0,p=[],l=Selector._children(d.parentNode,q),f;if(j){m=2;f="+";c="n";k=(j==="odd")?1:0;}else{if(isNaN(m)){m=(c)?1:0;
+}}if(m===0){if(h){k=l.length-k+1;}if(l[k-1]===d){return true;}else{return false;}}else{if(m<0){h=!!h;m=Math.abs(m);}}if(!h){for(var e=k-1,g=l.length;e<g;e+=m){if(e>=0&&l[e]===d){return true;}}}else{for(var e=l.length-k,g=l.length;e>=0;e-=m){if(e<g&&l[e]===d){return true;}}}return false;};Y_mix(Selector.pseudos,{"root":function(a){return a===a.ownerDocument.documentElement;},"nth-child":function(a,b){return Selector._getNth(a,b);},"nth-last-child":function(a,b){return Selector._getNth(a,b,null,true);},"nth-of-type":function(a,b){return Selector._getNth(a,b,a.tagName);},"nth-last-of-type":function(a,b){return Selector._getNth(a,b,a.tagName,true);},"last-child":function(b){var a=Selector._children(b.parentNode);return a[a.length-1]===b;},"first-of-type":function(a){return Selector._children(a.parentNode,a.tagName)[0]===a;},"last-of-type":function(b){var a=Selector._children(b.parentNode,b.tagName);return a[a.length-1]===b;},"only-child":function(b){var a=Selector._children(b.parentNode);return a.length===1&&a[0]===b;},"only-of-type":function(b){var a=Selector._children(b.parentNode,b.tagName);return a.length===1&&a[0]===b;},"empty":function(a){return a.childNodes.length===0;},"not":function(a,b){return !Selector.test(a,b);},"contains":function(a,b){var c=a.innerText||a.textContent||"";return c.indexOf(b)>-1;},"checked":function(a){return(a.checked===true||a.selected===true);},enabled:function(a){return(a.disabled!==undefined&&!a.disabled);},disabled:function(a){return(a.disabled);}});Y_mix(Selector.operators,{"^=":"^{val}","!=":function(b,a,c){return b[a]!==c;},"$=":"{val}$","*=":"{val}"});Selector.combinators["~"]={axis:"previousSibling"};YAHOO.register("selector",YAHOO.util.Selector,{version:"2.9.0",build:"2800"});var Dom=YAHOO.util.Dom;YAHOO.widget.ColumnSet=function(a){this._sId=Dom.generateId(null,"yui-cs");a=YAHOO.widget.DataTable._cloneObject(a);this._init(a);YAHOO.widget.ColumnSet._nCount++;};YAHOO.widget.ColumnSet._nCount=0;YAHOO.widget.ColumnSet.prototype={_sId:null,_aDefinitions:null,tree:null,flat:null,keys:null,headers:null,_init:function(j){var k=[];var a=[];var g=[];var e=[];var c=-1;var b=function(m,s){c++;if(!k[c]){k[c]=[];}for(var o=0;o<m.length;o++){var i=m[o];var q=new YAHOO.widget.Column(i);i.yuiColumnId=q._sId;a.push(q);if(s){q._oParent=s;}if(YAHOO.lang.isArray(i.children)){q.children=i.children;var r=0;var p=function(v){var w=v.children;for(var u=0;u<w.length;u++){if(YAHOO.lang.isArray(w[u].children)){p(w[u]);}else{r++;}}};p(i);q._nColspan=r;var t=i.children;for(var n=0;n<t.length;n++){var l=t[n];if(q.className&&(l.className===undefined)){l.className=q.className;}if(q.editor&&(l.editor===undefined)){l.editor=q.editor;}if(q.editorOptions&&(l.editorOptions===undefined)){l.editorOptions=q.editorOptions;}if(q.formatter&&(l.formatter===undefined)){l.formatter=q.formatter;}if(q.resizeable&&(l.resizeable===undefined)){l.resizeable=q.resizeable;}if(q.sortable&&(l.sortable===undefined)){l.sortable=q.sortable;}if(q.hidden){l.hidden=true;}if(q.width&&(l.width===undefined)){l.width=q.width;}if(q.minWidth&&(l.minWidth===undefined)){l.minWidth=q.minWidth;}if(q.maxAutoWidth&&(l.maxAutoWidth===undefined)){l.maxAutoWidth=q.maxAutoWidth;}if(q.type&&(l.type===undefined)){l.type=q.type;}if(q.type&&!q.formatter){q.formatter=q.type;}if(q.text&&!YAHOO.lang.isValue(q.label)){q.label=q.text;}if(q.parser){}if(q.sortOptions&&((q.sortOptions.ascFunction)||(q.sortOptions.descFunction))){}}if(!k[c+1]){k[c+1]=[];}b(t,q);}else{q._nKeyIndex=g.length;q._nColspan=1;g.push(q);}k[c].push(q);}c--;};if(YAHOO.lang.isArray(j)){b(j);this._aDefinitions=j;}else{return null;}var f;var d=function(l){var n=1;var q;var o;var r=function(t,p){p=p||1;for(var u=0;u<t.length;u++){var m=t[u];if(YAHOO.lang.isArray(m.children)){p++;r(m.children,p);p--;}else{if(p>n){n=p;}}}};for(var i=0;i<l.length;i++){q=l[i];r(q);for(var s=0;s<q.length;s++){o=q[s];if(!YAHOO.lang.isArray(o.children)){o._nRowspan=n;}else{o._nRowspan=1;}}n=1;}};d(k);for(f=0;f<k[0].length;f++){k[0][f]._nTreeIndex=f;}var h=function(l,m){e[l].push(m.getSanitizedKey());if(m._oParent){h(l,m._oParent);}};for(f=0;f<g.length;f++){e[f]=[];h(f,g[f]);e[f]=e[f].reverse();}this.tree=k;this.flat=a;this.keys=g;this.headers=e;},getId:function(){return this._sId;},toString:function(){return"ColumnSet instance "+this._sId;},getDefinitions:function(){var a=this._aDefinitions;var b=function(e,g){for(var d=0;d<e.length;d++){var f=e[d];var i=g.getColumnById(f.yuiColumnId);if(i){var h=i.getDefinition();for(var c in h){if(YAHOO.lang.hasOwnProperty(h,c)){f[c]=h[c];}}}if(YAHOO.lang.isArray(f.children)){b(f.children,g);}}};b(a,this);this._aDefinitions=a;return a;},getColumnById:function(c){if(YAHOO.lang.isString(c)){var a=this.flat;for(var b=a.length-1;b>-1;b--){if(a[b]._sId===c){return a[b];}}}return null;},getColumn:function(c){if(YAHOO.lang.isNumber(c)&&this.keys[c]){return this.keys[c];}else{if(YAHOO.lang.isString(c)){var a=this.flat;var d=[];for(var b=0;b<a.length;b++){if(a[b].key===c){d.push(a[b]);}}if(d.length===1){return d[0];}else{if(d.length>1){return d;}}}}return null;},getDescendants:function(d){var b=this;var c=[];var a;var e=function(f){c.push(f);if(f.children){for(a=0;a<f.children.length;a++){e(b.getColumn(f.children[a].key));}}};e(d);return c;}};YAHOO.widget.Column=function(b){this._sId=Dom.generateId(null,"yui-col");if(b&&YAHOO.lang.isObject(b)){for(var a in b){if(a){this[a]=b[a];}}}if(!YAHOO.lang.isValue(this.key)){this.key=Dom.generateId(null,"yui-dt-col");}if(!YAHOO.lang.isValue(this.field)){this.field=this.key;}YAHOO.widget.Column._nCount++;if(this.width&&!YAHOO.lang.isNumber(this.width)){this.width=null;}if(this.editor&&YAHOO.lang.isString(this.editor)){this.editor=new YAHOO.widget.CellEditor(this.editor,this.editorOptions);}};YAHOO.lang.augmentObject(YAHOO.widget.Column,{_nCount:0,formatCheckbox:function(b,a,c,d){YAHOO.widget.DataTable.formatCheckbox(b,a,c,d);},formatCurrency:function(b,a,c,d){YAHOO.widget.DataTable.formatCurrency(b,a,c,d);},formatDate:function(b,a,c,d){YAHOO.widget.DataTable.formatDate(b,a,c,d);
+},formatEmail:function(b,a,c,d){YAHOO.widget.DataTable.formatEmail(b,a,c,d);},formatLink:function(b,a,c,d){YAHOO.widget.DataTable.formatLink(b,a,c,d);},formatNumber:function(b,a,c,d){YAHOO.widget.DataTable.formatNumber(b,a,c,d);},formatSelect:function(b,a,c,d){YAHOO.widget.DataTable.formatDropdown(b,a,c,d);}});YAHOO.widget.Column.prototype={_sId:null,_nKeyIndex:null,_nTreeIndex:null,_nColspan:1,_nRowspan:1,_oParent:null,_elTh:null,_elThLiner:null,_elThLabel:null,_elResizer:null,_nWidth:null,_dd:null,_ddResizer:null,key:null,field:null,label:null,abbr:null,children:null,width:null,minWidth:null,maxAutoWidth:null,hidden:false,selected:false,className:null,formatter:null,currencyOptions:null,dateOptions:null,dropdownOptions:null,editor:null,resizeable:false,sortable:false,sortOptions:null,getId:function(){return this._sId;},toString:function(){return"Column instance "+this._sId;},getDefinition:function(){var a={};a.abbr=this.abbr;a.className=this.className;a.editor=this.editor;a.editorOptions=this.editorOptions;a.field=this.field;a.formatter=this.formatter;a.hidden=this.hidden;a.key=this.key;a.label=this.label;a.minWidth=this.minWidth;a.maxAutoWidth=this.maxAutoWidth;a.resizeable=this.resizeable;a.selected=this.selected;a.sortable=this.sortable;a.sortOptions=this.sortOptions;a.width=this.width;a._calculatedWidth=this._calculatedWidth;return a;},getKey:function(){return this.key;},getField:function(){return this.field;},getSanitizedKey:function(){return this.getKey().replace(/[^\w\-]/g,"");},getKeyIndex:function(){return this._nKeyIndex;},getTreeIndex:function(){return this._nTreeIndex;},getParent:function(){return this._oParent;},getColspan:function(){return this._nColspan;},getColSpan:function(){return this.getColspan();},getRowspan:function(){return this._nRowspan;},getThEl:function(){return this._elTh;},getThLinerEl:function(){return this._elThLiner;},getResizerEl:function(){return this._elResizer;},getColEl:function(){return this.getThEl();},getIndex:function(){return this.getKeyIndex();},format:function(){}};YAHOO.util.Sort={compare:function(d,c,e){if((d===null)||(typeof d=="undefined")){if((c===null)||(typeof c=="undefined")){return 0;}else{return 1;}}else{if((c===null)||(typeof c=="undefined")){return -1;}}if(d.constructor==String){d=d.toLowerCase();}if(c.constructor==String){c=c.toLowerCase();}if(d<c){return(e)?1:-1;}else{if(d>c){return(e)?-1:1;}else{return 0;}}}};YAHOO.widget.ColumnDD=function(d,a,c,b){if(d&&a&&c&&b){this.datatable=d;this.table=d.getTableEl();this.column=a;this.headCell=c;this.pointer=b;this.newIndex=null;this.init(c);this.initFrame();this.invalidHandleTypes={};this.setPadding(10,0,(this.datatable.getTheadEl().offsetHeight+10),0);YAHOO.util.Event.on(window,"resize",function(){this.initConstraints();},this,true);}else{}};if(YAHOO.util.DDProxy){YAHOO.extend(YAHOO.widget.ColumnDD,YAHOO.util.DDProxy,{initConstraints:function(){var g=YAHOO.util.Dom.getRegion(this.table),d=this.getEl(),f=YAHOO.util.Dom.getXY(d),c=parseInt(YAHOO.util.Dom.getStyle(d,"width"),10),a=parseInt(YAHOO.util.Dom.getStyle(d,"height"),10),e=((f[0]-g.left)+15),b=((g.right-f[0]-c)+15);this.setXConstraint(e,b);this.setYConstraint(10,10);},_resizeProxy:function(){YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this,arguments);var a=this.getDragEl(),b=this.getEl();YAHOO.util.Dom.setStyle(this.pointer,"height",(this.table.parentNode.offsetHeight+10)+"px");YAHOO.util.Dom.setStyle(this.pointer,"display","block");var c=YAHOO.util.Dom.getXY(b);YAHOO.util.Dom.setXY(this.pointer,[c[0],(c[1]-5)]);YAHOO.util.Dom.setStyle(a,"height",this.datatable.getContainerEl().offsetHeight+"px");YAHOO.util.Dom.setStyle(a,"width",(parseInt(YAHOO.util.Dom.getStyle(a,"width"),10)+4)+"px");YAHOO.util.Dom.setXY(this.dragEl,c);},onMouseDown:function(){this.initConstraints();this.resetConstraints();},clickValidator:function(b){if(!this.column.hidden){var a=YAHOO.util.Event.getTarget(b);return(this.isValidHandleChild(a)&&(this.id==this.handleElId||this.DDM.handleWasClicked(a,this.id)));}},onDragOver:function(h,a){var f=this.datatable.getColumn(a);if(f){var c=f.getTreeIndex();while((c===null)&&f.getParent()){f=f.getParent();c=f.getTreeIndex();}if(c!==null){var b=f.getThEl();var k=c;var d=YAHOO.util.Event.getPageX(h),i=YAHOO.util.Dom.getX(b),j=i+((YAHOO.util.Dom.get(b).offsetWidth)/2),e=this.column.getTreeIndex();if(d<j){YAHOO.util.Dom.setX(this.pointer,i);}else{var g=parseInt(b.offsetWidth,10);YAHOO.util.Dom.setX(this.pointer,(i+g));k++;}if(c>e){k--;}if(k<0){k=0;}else{if(k>this.datatable.getColumnSet().tree[0].length){k=this.datatable.getColumnSet().tree[0].length;}}this.newIndex=k;}}},onDragDrop:function(){this.datatable.reorderColumn(this.column,this.newIndex);},endDrag:function(){this.newIndex=null;YAHOO.util.Dom.setStyle(this.pointer,"display","none");}});}YAHOO.util.ColumnResizer=function(e,c,d,a,b){if(e&&c&&d&&a){this.datatable=e;this.column=c;this.headCell=d;this.headCellLiner=c.getThLinerEl();this.resizerLiner=d.firstChild;this.init(a,a,{dragOnly:true,dragElId:b.id});this.initFrame();this.resetResizerEl();this.setPadding(0,1,0,0);}else{}};if(YAHOO.util.DD){YAHOO.extend(YAHOO.util.ColumnResizer,YAHOO.util.DDProxy,{resetResizerEl:function(){var a=YAHOO.util.Dom.get(this.handleElId).style;a.left="auto";a.right=0;a.top="auto";a.bottom=0;a.height=this.headCell.offsetHeight+"px";},onMouseUp:function(h){var f=this.datatable.getColumnSet().keys,b;for(var c=0,a=f.length;c<a;c++){b=f[c];if(b._ddResizer){b._ddResizer.resetResizerEl();}}this.resetResizerEl();var d=this.headCellLiner;var g=d.offsetWidth-(parseInt(YAHOO.util.Dom.getStyle(d,"paddingLeft"),10)|0)-(parseInt(YAHOO.util.Dom.getStyle(d,"paddingRight"),10)|0);this.datatable.fireEvent("columnResizeEvent",{column:this.column,target:this.headCell,width:g});},onMouseDown:function(a){this.startWidth=this.headCellLiner.offsetWidth;this.startX=YAHOO.util.Event.getXY(a)[0];this.nLinerPadding=(parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0)+(parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
+},clickValidator:function(b){if(!this.column.hidden){var a=YAHOO.util.Event.getTarget(b);return(this.isValidHandleChild(a)&&(this.id==this.handleElId||this.DDM.handleWasClicked(a,this.id)));}},startDrag:function(){var e=this.datatable.getColumnSet().keys,d=this.column.getKeyIndex(),b;for(var c=0,a=e.length;c<a;c++){b=e[c];if(b._ddResizer){YAHOO.util.Dom.get(b._ddResizer.handleElId).style.height="1em";}}},onDrag:function(c){var d=YAHOO.util.Event.getXY(c)[0];if(d>YAHOO.util.Dom.getX(this.headCellLiner)){var a=d-this.startX;var b=this.startWidth+a-this.nLinerPadding;if(b>0){this.datatable.setColumnWidth(this.column,b);}}}});}(function(){var g=YAHOO.lang,a=YAHOO.util,e=YAHOO.widget,c=a.Dom,f=a.Event,d=e.DataTable;YAHOO.widget.RecordSet=function(h){this._init(h);};var b=e.RecordSet;b._nCount=0;b.prototype={_sId:null,_init:function(h){this._sId=c.generateId(null,"yui-rs");e.RecordSet._nCount++;this._records=[];this._initEvents();if(h){if(g.isArray(h)){this.addRecords(h);}else{if(g.isObject(h)){this.addRecord(h);}}}},_initEvents:function(){this.createEvent("recordAddEvent");this.createEvent("recordsAddEvent");this.createEvent("recordSetEvent");this.createEvent("recordsSetEvent");this.createEvent("recordUpdateEvent");this.createEvent("recordDeleteEvent");this.createEvent("recordsDeleteEvent");this.createEvent("resetEvent");this.createEvent("recordValueUpdateEvent");},_addRecord:function(j,h){var i=new YAHOO.widget.Record(j);if(YAHOO.lang.isNumber(h)&&(h>-1)){this._records.splice(h,0,i);}else{this._records[this._records.length]=i;}return i;},_setRecord:function(i,h){if(!g.isNumber(h)||h<0){h=this._records.length;}return(this._records[h]=new e.Record(i));},_deleteRecord:function(i,h){if(!g.isNumber(h)||(h<0)){h=1;}this._records.splice(i,h);},getId:function(){return this._sId;},toString:function(){return"RecordSet instance "+this._sId;},getLength:function(){return this._records.length;},getRecord:function(h){var j;if(h instanceof e.Record){for(j=0;j<this._records.length;j++){if(this._records[j]&&(this._records[j]._sId===h._sId)){return h;}}}else{if(g.isNumber(h)){if((h>-1)&&(h<this.getLength())){return this._records[h];}}else{if(g.isString(h)){for(j=0;j<this._records.length;j++){if(this._records[j]&&(this._records[j]._sId===h)){return this._records[j];}}}}}return null;},getRecords:function(i,h){if(!g.isNumber(i)){return this._records;}if(!g.isNumber(h)){return this._records.slice(i);}return this._records.slice(i,i+h);},hasRecords:function(j,h){var l=this.getRecords(j,h);for(var k=0;k<h;++k){if(typeof l[k]==="undefined"){return false;}}return true;},getRecordIndex:function(j){if(j){for(var h=this._records.length-1;h>-1;h--){if(this._records[h]&&j.getId()===this._records[h].getId()){return h;}}}return null;},addRecord:function(j,h){if(g.isObject(j)){var i=this._addRecord(j,h);this.fireEvent("recordAddEvent",{record:i,data:j});return i;}else{return null;}},addRecords:function(m,l){if(g.isArray(m)){var p=[],j,n,h;l=g.isNumber(l)?l:this._records.length;j=l;for(n=0,h=m.length;n<h;++n){if(g.isObject(m[n])){var k=this._addRecord(m[n],j++);p.push(k);}}this.fireEvent("recordsAddEvent",{records:p,data:m});return p;}else{if(g.isObject(m)){var o=this._addRecord(m);this.fireEvent("recordsAddEvent",{records:[o],data:m});return o;}else{return null;}}},setRecord:function(j,h){if(g.isObject(j)){var i=this._setRecord(j,h);this.fireEvent("recordSetEvent",{record:i,data:j});return i;}else{return null;}},setRecords:function(o,n){var r=e.Record,k=g.isArray(o)?o:[o],q=[],p=0,h=k.length,m=0;n=parseInt(n,10)|0;for(;p<h;++p){if(typeof k[p]==="object"&&k[p]){q[m++]=this._records[n+p]=new r(k[p]);}}this.fireEvent("recordsSetEvent",{records:q,data:o});this.fireEvent("recordsSet",{records:q,data:o});if(k.length&&!q.length){}return q;},updateRecord:function(h,l){var j=this.getRecord(h);if(j&&g.isObject(l)){var k={};for(var i in j._oData){if(g.hasOwnProperty(j._oData,i)){k[i]=j._oData[i];}}j._oData=l;this.fireEvent("recordUpdateEvent",{record:j,newData:l,oldData:k});return j;}else{return null;}},updateKey:function(h,i,j){this.updateRecordValue(h,i,j);},updateRecordValue:function(h,k,n){var j=this.getRecord(h);if(j){var m=null;var l=j._oData[k];if(l&&g.isObject(l)){m={};for(var i in l){if(g.hasOwnProperty(l,i)){m[i]=l[i];}}}else{m=l;}j._oData[k]=n;this.fireEvent("keyUpdateEvent",{record:j,key:k,newData:n,oldData:m});this.fireEvent("recordValueUpdateEvent",{record:j,key:k,newData:n,oldData:m});}else{}},replaceRecords:function(h){this.reset();return this.addRecords(h);},sortRecords:function(h,j,i){return this._records.sort(function(l,k){return h(l,k,j,i);});},reverseRecords:function(){return this._records.reverse();},deleteRecord:function(h){if(g.isNumber(h)&&(h>-1)&&(h<this.getLength())){var i=this.getRecord(h).getData();this._deleteRecord(h);this.fireEvent("recordDeleteEvent",{data:i,index:h});return i;}else{return null;}},deleteRecords:function(k,h){if(!g.isNumber(h)){h=1;}if(g.isNumber(k)&&(k>-1)&&(k<this.getLength())){var m=this.getRecords(k,h);var j=[],n=[];for(var l=0;l<m.length;l++){j[j.length]=m[l];n[n.length]=m[l].getData();}this._deleteRecord(k,h);this.fireEvent("recordsDeleteEvent",{data:j,deletedData:n,index:k});return j;}else{return null;}},reset:function(){this._records=[];this.fireEvent("resetEvent");}};g.augmentProto(b,a.EventProvider);YAHOO.widget.Record=function(h){this._nCount=e.Record._nCount;this._sId=c.generateId(null,"yui-rec");e.Record._nCount++;this._oData={};if(g.isObject(h)){for(var i in h){if(g.hasOwnProperty(h,i)){this._oData[i]=h[i];}}}};YAHOO.widget.Record._nCount=0;YAHOO.widget.Record.prototype={_nCount:null,_sId:null,_oData:null,getCount:function(){return this._nCount;},getId:function(){return this._sId;},getData:function(h){if(g.isString(h)){return this._oData[h];}else{return this._oData;}},setData:function(h,i){this._oData[h]=i;}};})();(function(){var h=YAHOO.lang,a=YAHOO.util,e=YAHOO.widget,b=YAHOO.env.ua,c=a.Dom,g=a.Event,f=a.DataSourceBase;YAHOO.widget.DataTable=function(i,m,o,k){var l=e.DataTable;
+if(k&&k.scrollable){return new YAHOO.widget.ScrollingDataTable(i,m,o,k);}this._nIndex=l._nCount;this._sId=c.generateId(null,"yui-dt");this._oChainRender=new YAHOO.util.Chain();this._oChainRender.subscribe("end",this._onRenderChainEnd,this,true);this._initConfigs(k);this._initDataSource(o);if(!this._oDataSource){return;}this._initColumnSet(m);if(!this._oColumnSet){return;}this._initRecordSet();if(!this._oRecordSet){}l.superclass.constructor.call(this,i,this.configs);var q=this._initDomElements(i);if(!q){return;}this.showTableMessage(this.get("MSG_LOADING"),l.CLASS_LOADING);this._initEvents();l._nCount++;l._nCurrentCount++;var n={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,scope:this,argument:this.getState()};var p=this.get("initialLoad");if(p===true){this._oDataSource.sendRequest(this.get("initialRequest"),n);}else{if(p===false){this.showTableMessage(this.get("MSG_EMPTY"),l.CLASS_EMPTY);}else{var j=p||{};n.argument=j.argument||{};this._oDataSource.sendRequest(j.request,n);}}};var d=e.DataTable;h.augmentObject(d,{CLASS_DATATABLE:"yui-dt",CLASS_LINER:"yui-dt-liner",CLASS_LABEL:"yui-dt-label",CLASS_MESSAGE:"yui-dt-message",CLASS_MASK:"yui-dt-mask",CLASS_DATA:"yui-dt-data",CLASS_COLTARGET:"yui-dt-coltarget",CLASS_RESIZER:"yui-dt-resizer",CLASS_RESIZERLINER:"yui-dt-resizerliner",CLASS_RESIZERPROXY:"yui-dt-resizerproxy",CLASS_EDITOR:"yui-dt-editor",CLASS_EDITOR_SHIM:"yui-dt-editor-shim",CLASS_PAGINATOR:"yui-dt-paginator",CLASS_PAGE:"yui-dt-page",CLASS_DEFAULT:"yui-dt-default",CLASS_PREVIOUS:"yui-dt-previous",CLASS_NEXT:"yui-dt-next",CLASS_FIRST:"yui-dt-first",CLASS_LAST:"yui-dt-last",CLASS_REC:"yui-dt-rec",CLASS_EVEN:"yui-dt-even",CLASS_ODD:"yui-dt-odd",CLASS_SELECTED:"yui-dt-selected",CLASS_HIGHLIGHTED:"yui-dt-highlighted",CLASS_HIDDEN:"yui-dt-hidden",CLASS_DISABLED:"yui-dt-disabled",CLASS_EMPTY:"yui-dt-empty",CLASS_LOADING:"yui-dt-loading",CLASS_ERROR:"yui-dt-error",CLASS_EDITABLE:"yui-dt-editable",CLASS_DRAGGABLE:"yui-dt-draggable",CLASS_RESIZEABLE:"yui-dt-resizeable",CLASS_SCROLLABLE:"yui-dt-scrollable",CLASS_SORTABLE:"yui-dt-sortable",CLASS_ASC:"yui-dt-asc",CLASS_DESC:"yui-dt-desc",CLASS_BUTTON:"yui-dt-button",CLASS_CHECKBOX:"yui-dt-checkbox",CLASS_DROPDOWN:"yui-dt-dropdown",CLASS_RADIO:"yui-dt-radio",_nCount:0,_nCurrentCount:0,_elDynStyleNode:null,_bDynStylesFallback:(b.ie)?true:false,_oDynStyles:{},_cloneObject:function(m){if(!h.isValue(m)){return m;}var p={};if(m instanceof YAHOO.widget.BaseCellEditor){p=m;}else{if(Object.prototype.toString.apply(m)==="[object RegExp]"){p=m;}else{if(h.isFunction(m)){p=m;}else{if(h.isArray(m)){var n=[];for(var l=0,k=m.length;l<k;l++){n[l]=d._cloneObject(m[l]);}p=n;}else{if(h.isObject(m)){for(var j in m){if(h.hasOwnProperty(m,j)){if(h.isValue(m[j])&&h.isObject(m[j])||h.isArray(m[j])){p[j]=d._cloneObject(m[j]);}else{p[j]=m[j];}}}}else{p=m;}}}}}return p;},formatButton:function(i,j,k,n,m){var l=h.isValue(n)?n:"Click";i.innerHTML='<button type="button" class="'+d.CLASS_BUTTON+'">'+l+"</button>";},formatCheckbox:function(i,j,k,n,m){var l=n;l=(l)?' checked="checked"':"";i.innerHTML='<input type="checkbox"'+l+' class="'+d.CLASS_CHECKBOX+'" />';},formatCurrency:function(j,k,l,n,m){var i=m||this;j.innerHTML=a.Number.format(n,l.currencyOptions||i.get("currencyOptions"));},formatDate:function(j,l,m,o,n){var i=n||this,k=m.dateOptions||i.get("dateOptions");j.innerHTML=a.Date.format(o,k,k.locale);},formatDropdown:function(l,u,q,j,t){var s=t||this,r=(h.isValue(j))?j:u.getData(q.field),v=(h.isArray(q.dropdownOptions))?q.dropdownOptions:null,k,p=l.getElementsByTagName("select");if(p.length===0){k=document.createElement("select");k.className=d.CLASS_DROPDOWN;k=l.appendChild(k);g.addListener(k,"change",s._onDropdownChange,s);}k=p[0];if(k){k.innerHTML="";if(v){for(var n=0;n<v.length;n++){var o=v[n];var m=document.createElement("option");m.value=(h.isValue(o.value))?o.value:o;m.innerHTML=(h.isValue(o.text))?o.text:(h.isValue(o.label))?o.label:o;m=k.appendChild(m);if(m.value==r){m.selected=true;}}}else{k.innerHTML='<option selected value="'+r+'">'+r+"</option>";}}else{l.innerHTML=h.isValue(j)?j:"";}},formatEmail:function(i,j,k,m,l){if(h.isString(m)){m=h.escapeHTML(m);i.innerHTML='<a href="mailto:'+m+'">'+m+"</a>";}else{i.innerHTML=h.isValue(m)?h.escapeHTML(m.toString()):"";}},formatLink:function(i,j,k,m,l){if(h.isString(m)){m=h.escapeHTML(m);i.innerHTML='<a href="'+m+'">'+m+"</a>";}else{i.innerHTML=h.isValue(m)?h.escapeHTML(m.toString()):"";}},formatNumber:function(j,k,l,n,m){var i=m||this;j.innerHTML=a.Number.format(n,l.numberOptions||i.get("numberOptions"));},formatRadio:function(j,k,l,o,n){var i=n||this,m=o;m=(m)?' checked="checked"':"";j.innerHTML='<input type="radio"'+m+' name="'+i.getId()+"-col-"+l.getSanitizedKey()+'"'+' class="'+d.CLASS_RADIO+'" />';},formatText:function(i,j,l,n,m){var k=(h.isValue(n))?n:"";i.innerHTML=h.escapeHTML(k.toString());},formatTextarea:function(j,k,m,o,n){var l=(h.isValue(o))?h.escapeHTML(o.toString()):"",i="<textarea>"+l+"</textarea>";j.innerHTML=i;},formatTextbox:function(j,k,m,o,n){var l=(h.isValue(o))?h.escapeHTML(o.toString()):"",i='<input type="text" value="'+l+'" />';j.innerHTML=i;},formatDefault:function(i,j,k,m,l){i.innerHTML=(h.isValue(m)&&m!=="")?m.toString():"&#160;";},validateNumber:function(j){var i=j*1;if(h.isNumber(i)){return i;}else{return undefined;}}});d.Formatter={button:d.formatButton,checkbox:d.formatCheckbox,currency:d.formatCurrency,"date":d.formatDate,dropdown:d.formatDropdown,email:d.formatEmail,link:d.formatLink,"number":d.formatNumber,radio:d.formatRadio,text:d.formatText,textarea:d.formatTextarea,textbox:d.formatTextbox,defaultFormatter:d.formatDefault};h.extend(d,a.Element,{initAttributes:function(i){i=i||{};d.superclass.initAttributes.call(this,i);this.setAttributeConfig("summary",{value:"",validator:h.isString,method:function(j){if(this._elTable){this._elTable.summary=j;}}});this.setAttributeConfig("selectionMode",{value:"standard",validator:h.isString});this.setAttributeConfig("sortedBy",{value:null,validator:function(j){if(j){return(h.isObject(j)&&j.key);
+}else{return(j===null);}},method:function(k){var r=this.get("sortedBy");this._configs.sortedBy.value=k;var j,o,m,q;if(this._elThead){if(r&&r.key&&r.dir){j=this._oColumnSet.getColumn(r.key);o=j.getKeyIndex();var u=j.getThEl();c.removeClass(u,r.dir);this.formatTheadCell(j.getThLinerEl().firstChild,j,k);}if(k){m=(k.column)?k.column:this._oColumnSet.getColumn(k.key);q=m.getKeyIndex();var v=m.getThEl();if(k.dir&&((k.dir=="asc")||(k.dir=="desc"))){var p=(k.dir=="desc")?d.CLASS_DESC:d.CLASS_ASC;c.addClass(v,p);}else{var l=k.dir||d.CLASS_ASC;c.addClass(v,l);}this.formatTheadCell(m.getThLinerEl().firstChild,m,k);}}if(this._elTbody){this._elTbody.style.display="none";var s=this._elTbody.rows,t;for(var n=s.length-1;n>-1;n--){t=s[n].childNodes;if(t[o]){c.removeClass(t[o],r.dir);}if(t[q]){c.addClass(t[q],k.dir);}}this._elTbody.style.display="";}this._clearTrTemplateEl();}});this.setAttributeConfig("paginator",{value:null,validator:function(j){return j===null||j instanceof e.Paginator;},method:function(){this._updatePaginator.apply(this,arguments);}});this.setAttributeConfig("caption",{value:null,validator:h.isString,method:function(j){this._initCaptionEl(j);}});this.setAttributeConfig("draggableColumns",{value:false,validator:h.isBoolean,method:function(j){if(this._elThead){if(j){this._initDraggableColumns();}else{this._destroyDraggableColumns();}}}});this.setAttributeConfig("renderLoopSize",{value:0,validator:h.isNumber});this.setAttributeConfig("sortFunction",{value:function(k,j,o,n){var m=YAHOO.util.Sort.compare,l=m(k.getData(n),j.getData(n),o);if(l===0){return m(k.getCount(),j.getCount(),o);}else{return l;}}});this.setAttributeConfig("formatRow",{value:null,validator:h.isFunction});this.setAttributeConfig("generateRequest",{value:function(k,n){k=k||{pagination:null,sortedBy:null};var m=encodeURIComponent((k.sortedBy)?k.sortedBy.key:n.getColumnSet().keys[0].getKey());var j=(k.sortedBy&&k.sortedBy.dir===YAHOO.widget.DataTable.CLASS_DESC)?"desc":"asc";var o=(k.pagination)?k.pagination.recordOffset:0;var l=(k.pagination)?k.pagination.rowsPerPage:null;return"sort="+m+"&dir="+j+"&startIndex="+o+((l!==null)?"&results="+l:"");},validator:h.isFunction});this.setAttributeConfig("initialRequest",{value:null});this.setAttributeConfig("initialLoad",{value:true});this.setAttributeConfig("dynamicData",{value:false,validator:h.isBoolean});this.setAttributeConfig("MSG_EMPTY",{value:"No records found.",validator:h.isString});this.setAttributeConfig("MSG_LOADING",{value:"Loading...",validator:h.isString});this.setAttributeConfig("MSG_ERROR",{value:"Data error.",validator:h.isString});this.setAttributeConfig("MSG_SORTASC",{value:"Click to sort ascending",validator:h.isString,method:function(k){if(this._elThead){for(var l=0,m=this.getColumnSet().keys,j=m.length;l<j;l++){if(m[l].sortable&&this.getColumnSortDir(m[l])===d.CLASS_ASC){m[l]._elThLabel.firstChild.title=k;}}}}});this.setAttributeConfig("MSG_SORTDESC",{value:"Click to sort descending",validator:h.isString,method:function(k){if(this._elThead){for(var l=0,m=this.getColumnSet().keys,j=m.length;l<j;l++){if(m[l].sortable&&this.getColumnSortDir(m[l])===d.CLASS_DESC){m[l]._elThLabel.firstChild.title=k;}}}}});this.setAttributeConfig("currencySymbol",{value:"$",validator:h.isString});this.setAttributeConfig("currencyOptions",{value:{prefix:this.get("currencySymbol"),decimalPlaces:2,decimalSeparator:".",thousandsSeparator:","}});this.setAttributeConfig("dateOptions",{value:{format:"%m/%d/%Y",locale:"en"}});this.setAttributeConfig("numberOptions",{value:{decimalPlaces:0,thousandsSeparator:","}});},_bInit:true,_nIndex:null,_nTrCount:0,_nTdCount:0,_sId:null,_oChainRender:null,_elContainer:null,_elMask:null,_elTable:null,_elCaption:null,_elColgroup:null,_elThead:null,_elTbody:null,_elMsgTbody:null,_elMsgTr:null,_elMsgTd:null,_elColumnDragTarget:null,_elColumnResizerProxy:null,_oDataSource:null,_oColumnSet:null,_oRecordSet:null,_oCellEditor:null,_sFirstTrId:null,_sLastTrId:null,_elTrTemplate:null,_aDynFunctions:[],_disabled:false,clearTextSelection:function(){var i;if(window.getSelection){i=window.getSelection();}else{if(document.getSelection){i=document.getSelection();}else{if(document.selection){i=document.selection;}}}if(i){if(i.empty){i.empty();}else{if(i.removeAllRanges){i.removeAllRanges();}else{if(i.collapse){i.collapse();}}}}},_focusEl:function(i){i=i||this._elTbody;setTimeout(function(){try{i.focus();}catch(j){}},0);},_repaintGecko:(b.gecko)?function(j){j=j||this._elContainer;var i=j.parentNode;var k=j.nextSibling;i.insertBefore(i.removeChild(j),k);}:function(){},_repaintOpera:(b.opera)?function(){if(b.opera){document.documentElement.className+=" ";document.documentElement.className=YAHOO.lang.trim(document.documentElement.className);}}:function(){},_repaintWebkit:(b.webkit)?function(j){j=j||this._elContainer;var i=j.parentNode;var k=j.nextSibling;i.insertBefore(i.removeChild(j),k);}:function(){},_initConfigs:function(i){if(!i||!h.isObject(i)){i={};}this.configs=i;},_initColumnSet:function(n){var m,k,j;if(this._oColumnSet){for(k=0,j=this._oColumnSet.keys.length;k<j;k++){m=this._oColumnSet.keys[k];d._oDynStyles["."+this.getId()+"-col-"+m.getSanitizedKey()+" ."+d.CLASS_LINER]=undefined;if(m.editor&&m.editor.unsubscribeAll){m.editor.unsubscribeAll();}}this._oColumnSet=null;this._clearTrTemplateEl();}if(h.isArray(n)){this._oColumnSet=new YAHOO.widget.ColumnSet(n);}else{if(n instanceof YAHOO.widget.ColumnSet){this._oColumnSet=n;}}var l=this._oColumnSet.keys;for(k=0,j=l.length;k<j;k++){m=l[k];if(m.editor&&m.editor.subscribe){m.editor.subscribe("showEvent",this._onEditorShowEvent,this,true);m.editor.subscribe("keydownEvent",this._onEditorKeydownEvent,this,true);m.editor.subscribe("revertEvent",this._onEditorRevertEvent,this,true);m.editor.subscribe("saveEvent",this._onEditorSaveEvent,this,true);m.editor.subscribe("cancelEvent",this._onEditorCancelEvent,this,true);m.editor.subscribe("blurEvent",this._onEditorBlurEvent,this,true);m.editor.subscribe("blockEvent",this._onEditorBlockEvent,this,true);
+m.editor.subscribe("unblockEvent",this._onEditorUnblockEvent,this,true);}}},_initDataSource:function(j){this._oDataSource=null;if(j&&(h.isFunction(j.sendRequest))){this._oDataSource=j;}else{var k=null;var o=this._elContainer;var l=0;if(o.hasChildNodes()){var n=o.childNodes;for(l=0;l<n.length;l++){if(n[l].nodeName&&n[l].nodeName.toLowerCase()=="table"){k=n[l];break;}}if(k){var m=[];for(;l<this._oColumnSet.keys.length;l++){m.push({key:this._oColumnSet.keys[l].key});}this._oDataSource=new f(k);this._oDataSource.responseType=f.TYPE_HTMLTABLE;this._oDataSource.responseSchema={fields:m};}}}},_initRecordSet:function(){if(this._oRecordSet){this._oRecordSet.reset();}else{this._oRecordSet=new YAHOO.widget.RecordSet();}},_initDomElements:function(i){this._initContainerEl(i);this._initTableEl(this._elContainer);this._initColgroupEl(this._elTable);this._initTheadEl(this._elTable);this._initMsgTbodyEl(this._elTable);this._initTbodyEl(this._elTable);if(!this._elContainer||!this._elTable||!this._elColgroup||!this._elThead||!this._elTbody||!this._elMsgTbody){return false;}else{return true;}},_destroyContainerEl:function(m){var k=this._oColumnSet.keys,l,j;c.removeClass(m,d.CLASS_DATATABLE);g.purgeElement(m);g.purgeElement(this._elThead,true);g.purgeElement(this._elTbody);g.purgeElement(this._elMsgTbody);l=m.getElementsByTagName("select");if(l.length){g.detachListener(l,"change");}for(j=k.length-1;j>=0;--j){if(k[j].editor){g.purgeElement(k[j].editor._elContainer);}}m.innerHTML="";this._elContainer=null;this._elColgroup=null;this._elThead=null;this._elTbody=null;},_initContainerEl:function(j){j=c.get(j);if(j&&j.nodeName&&(j.nodeName.toLowerCase()=="div")){this._destroyContainerEl(j);c.addClass(j,d.CLASS_DATATABLE);g.addListener(j,"focus",this._onTableFocus,this);g.addListener(j,"dblclick",this._onTableDblclick,this);this._elContainer=j;var i=document.createElement("div");i.className=d.CLASS_MASK;i.style.display="none";this._elMask=j.appendChild(i);}},_destroyTableEl:function(){var i=this._elTable;if(i){g.purgeElement(i,true);i.parentNode.removeChild(i);this._elCaption=null;this._elColgroup=null;this._elThead=null;this._elTbody=null;}},_initCaptionEl:function(i){if(this._elTable&&i){if(!this._elCaption){this._elCaption=this._elTable.createCaption();}this._elCaption.innerHTML=i;}else{if(this._elCaption){this._elCaption.parentNode.removeChild(this._elCaption);}}},_initTableEl:function(i){if(i){this._destroyTableEl();this._elTable=i.appendChild(document.createElement("table"));this._elTable.summary=this.get("summary");if(this.get("caption")){this._initCaptionEl(this.get("caption"));}g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"thead ."+d.CLASS_LABEL,this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"thead ."+d.CLASS_LABEL,this);g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"tbody.yui-dt-data>tr>td",this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"tbody.yui-dt-data>tr>td",this);g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"tbody.yui-dt-message>tr>td",this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"tbody.yui-dt-message>tr>td",this);}},_destroyColgroupEl:function(){var i=this._elColgroup;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elColgroup=null;}},_initColgroupEl:function(s){if(s){this._destroyColgroupEl();var l=this._aColIds||[],r=this._oColumnSet.keys,m=0,p=l.length,j,o,q=document.createDocumentFragment(),n=document.createElement("col");for(m=0,p=r.length;m<p;m++){o=r[m];j=q.appendChild(n.cloneNode(false));}var k=s.insertBefore(document.createElement("colgroup"),s.firstChild);k.appendChild(q);this._elColgroup=k;}},_insertColgroupColEl:function(i){if(h.isNumber(i)&&this._elColgroup){var j=this._elColgroup.childNodes[i]||null;this._elColgroup.insertBefore(document.createElement("col"),j);}},_removeColgroupColEl:function(i){if(h.isNumber(i)&&this._elColgroup&&this._elColgroup.childNodes[i]){this._elColgroup.removeChild(this._elColgroup.childNodes[i]);}},_reorderColgroupColEl:function(l,k){if(h.isArray(l)&&h.isNumber(k)&&this._elColgroup&&(this._elColgroup.childNodes.length>l[l.length-1])){var j,n=[];for(j=l.length-1;j>-1;j--){n.push(this._elColgroup.removeChild(this._elColgroup.childNodes[l[j]]));}var m=this._elColgroup.childNodes[k]||null;for(j=n.length-1;j>-1;j--){this._elColgroup.insertBefore(n[j],m);}}},_destroyTheadEl:function(){var j=this._elThead;if(j){var i=j.parentNode;g.purgeElement(j,true);this._destroyColumnHelpers();i.removeChild(j);this._elThead=null;}},_initTheadEl:function(v){v=v||this._elTable;if(v){this._destroyTheadEl();var q=(this._elColgroup)?v.insertBefore(document.createElement("thead"),this._elColgroup.nextSibling):v.appendChild(document.createElement("thead"));g.addListener(q,"focus",this._onTheadFocus,this);g.addListener(q,"keydown",this._onTheadKeydown,this);g.addListener(q,"mousedown",this._onTableMousedown,this);g.addListener(q,"mouseup",this._onTableMouseup,this);g.addListener(q,"click",this._onTheadClick,this);var x=this._oColumnSet,t,r,p,n;var w=x.tree;var o;for(r=0;r<w.length;r++){var m=q.appendChild(document.createElement("tr"));for(p=0;p<w[r].length;p++){t=w[r][p];o=m.appendChild(document.createElement("th"));this._initThEl(o,t);}if(r===0){c.addClass(m,d.CLASS_FIRST);}if(r===(w.length-1)){c.addClass(m,d.CLASS_LAST);}}var k=x.headers[0]||[];for(r=0;r<k.length;r++){c.addClass(c.get(this.getId()+"-th-"+k[r]),d.CLASS_FIRST);}var s=x.headers[x.headers.length-1]||[];for(r=0;r<s.length;r++){c.addClass(c.get(this.getId()+"-th-"+s[r]),d.CLASS_LAST);}if(b.webkit&&b.webkit<420){var u=this;setTimeout(function(){q.style.display="";},0);q.style.display="none";}this._elThead=q;this._initColumnHelpers();}},_initThEl:function(m,l){m.id=this.getId()+"-th-"+l.getSanitizedKey();m.innerHTML="";m.rowSpan=l.getRowspan();m.colSpan=l.getColspan();l._elTh=m;var i=m.appendChild(document.createElement("div"));i.id=m.id+"-liner";i.className=d.CLASS_LINER;l._elThLiner=i;var j=i.appendChild(document.createElement("span"));
+j.className=d.CLASS_LABEL;if(l.abbr){m.abbr=l.abbr;}if(l.hidden){this._clearMinWidth(l);}m.className=this._getColumnClassNames(l);if(l.width){var k=(l.minWidth&&(l.width<l.minWidth))?l.minWidth:l.width;if(d._bDynStylesFallback){m.firstChild.style.overflow="hidden";m.firstChild.style.width=k+"px";}else{this._setColumnWidthDynStyles(l,k+"px","hidden");}}this.formatTheadCell(j,l,this.get("sortedBy"));l._elThLabel=j;},formatTheadCell:function(i,m,k){var q=m.getKey();var p=h.isValue(m.label)?m.label:q;if(m.sortable){var n=this.getColumnSortDir(m,k);var j=(n===d.CLASS_DESC);if(k&&(m.key===k.key)){j=!(k.dir===d.CLASS_DESC);}var l=this.getId()+"-href-"+m.getSanitizedKey();var o=(j)?this.get("MSG_SORTDESC"):this.get("MSG_SORTASC");i.innerHTML='<a href="'+l+'" title="'+o+'" class="'+d.CLASS_SORTABLE+'">'+p+"</a>";}else{i.innerHTML=p;}},_destroyDraggableColumns:function(){var l,m;for(var k=0,j=this._oColumnSet.tree[0].length;k<j;k++){l=this._oColumnSet.tree[0][k];if(l._dd){l._dd=l._dd.unreg();c.removeClass(l.getThEl(),d.CLASS_DRAGGABLE);}}this._destroyColumnDragTargetEl();},_initDraggableColumns:function(){this._destroyDraggableColumns();if(a.DD){var m,n,k;for(var l=0,j=this._oColumnSet.tree[0].length;l<j;l++){m=this._oColumnSet.tree[0][l];n=m.getThEl();c.addClass(n,d.CLASS_DRAGGABLE);k=this._initColumnDragTargetEl();m._dd=new YAHOO.widget.ColumnDD(this,m,n,k);}}else{}},_destroyColumnDragTargetEl:function(){if(this._elColumnDragTarget){var i=this._elColumnDragTarget;YAHOO.util.Event.purgeElement(i);i.parentNode.removeChild(i);this._elColumnDragTarget=null;}},_initColumnDragTargetEl:function(){if(!this._elColumnDragTarget){var i=document.createElement("div");i.id=this.getId()+"-coltarget";i.className=d.CLASS_COLTARGET;i.style.display="none";document.body.insertBefore(i,document.body.firstChild);this._elColumnDragTarget=i;}return this._elColumnDragTarget;},_destroyResizeableColumns:function(){var k=this._oColumnSet.keys;for(var l=0,j=k.length;l<j;l++){if(k[l]._ddResizer){k[l]._ddResizer=k[l]._ddResizer.unreg();c.removeClass(k[l].getThEl(),d.CLASS_RESIZEABLE);}}this._destroyColumnResizerProxyEl();},_initResizeableColumns:function(){this._destroyResizeableColumns();if(a.DD){var p,k,n,q,j,r,m;for(var l=0,o=this._oColumnSet.keys.length;l<o;l++){p=this._oColumnSet.keys[l];if(p.resizeable){k=p.getThEl();c.addClass(k,d.CLASS_RESIZEABLE);n=p.getThLinerEl();q=k.appendChild(document.createElement("div"));q.className=d.CLASS_RESIZERLINER;q.appendChild(n);j=q.appendChild(document.createElement("div"));j.id=k.id+"-resizer";j.className=d.CLASS_RESIZER;p._elResizer=j;r=this._initColumnResizerProxyEl();p._ddResizer=new YAHOO.util.ColumnResizer(this,p,k,j,r);m=function(i){g.stopPropagation(i);};g.addListener(j,"click",m);}}}else{}},_destroyColumnResizerProxyEl:function(){if(this._elColumnResizerProxy){var i=this._elColumnResizerProxy;YAHOO.util.Event.purgeElement(i);i.parentNode.removeChild(i);this._elColumnResizerProxy=null;}},_initColumnResizerProxyEl:function(){if(!this._elColumnResizerProxy){var i=document.createElement("div");i.id=this.getId()+"-colresizerproxy";i.className=d.CLASS_RESIZERPROXY;document.body.insertBefore(i,document.body.firstChild);this._elColumnResizerProxy=i;}return this._elColumnResizerProxy;},_destroyColumnHelpers:function(){this._destroyDraggableColumns();this._destroyResizeableColumns();},_initColumnHelpers:function(){if(this.get("draggableColumns")){this._initDraggableColumns();}this._initResizeableColumns();},_destroyTbodyEl:function(){var i=this._elTbody;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elTbody=null;}},_initTbodyEl:function(j){if(j){this._destroyTbodyEl();var i=j.appendChild(document.createElement("tbody"));i.tabIndex=0;i.className=d.CLASS_DATA;g.addListener(i,"focus",this._onTbodyFocus,this);g.addListener(i,"mousedown",this._onTableMousedown,this);g.addListener(i,"mouseup",this._onTableMouseup,this);g.addListener(i,"keydown",this._onTbodyKeydown,this);g.addListener(i,"click",this._onTbodyClick,this);if(b.ie){i.hideFocus=true;}this._elTbody=i;}},_destroyMsgTbodyEl:function(){var i=this._elMsgTbody;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elTbody=null;}},_initMsgTbodyEl:function(l){if(l){var k=document.createElement("tbody");k.className=d.CLASS_MESSAGE;var j=k.appendChild(document.createElement("tr"));j.className=d.CLASS_FIRST+" "+d.CLASS_LAST;this._elMsgTr=j;var m=j.appendChild(document.createElement("td"));m.colSpan=this._oColumnSet.keys.length||1;m.className=d.CLASS_FIRST+" "+d.CLASS_LAST;this._elMsgTd=m;k=l.insertBefore(k,this._elTbody);var i=m.appendChild(document.createElement("div"));i.className=d.CLASS_LINER;this._elMsgTbody=k;g.addListener(k,"focus",this._onTbodyFocus,this);g.addListener(k,"mousedown",this._onTableMousedown,this);g.addListener(k,"mouseup",this._onTableMouseup,this);g.addListener(k,"keydown",this._onTbodyKeydown,this);g.addListener(k,"click",this._onTbodyClick,this);}},_initEvents:function(){this._initColumnSort();YAHOO.util.Event.addListener(document,"click",this._onDocumentClick,this);this.subscribe("paginatorChange",function(){this._handlePaginatorChange.apply(this,arguments);});this.subscribe("initEvent",function(){this.renderPaginator();});this._initCellEditing();},_initColumnSort:function(){this.subscribe("theadCellClickEvent",this.onEventSortColumn);var i=this.get("sortedBy");if(i){if(i.dir=="desc"){this._configs.sortedBy.value.dir=d.CLASS_DESC;}else{if(i.dir=="asc"){this._configs.sortedBy.value.dir=d.CLASS_ASC;}}}},_initCellEditing:function(){this.subscribe("editorBlurEvent",function(){this.onEditorBlurEvent.apply(this,arguments);});this.subscribe("editorBlockEvent",function(){this.onEditorBlockEvent.apply(this,arguments);});this.subscribe("editorUnblockEvent",function(){this.onEditorUnblockEvent.apply(this,arguments);});},_getColumnClassNames:function(l,k){var i;if(h.isString(l.className)){i=[l.className];}else{if(h.isArray(l.className)){i=l.className;}else{i=[];}}i[i.length]=this.getId()+"-col-"+l.getSanitizedKey();
+i[i.length]="yui-dt-col-"+l.getSanitizedKey();var j=this.get("sortedBy")||{};if(l.key===j.key){i[i.length]=j.dir||"";}if(l.hidden){i[i.length]=d.CLASS_HIDDEN;}if(l.selected){i[i.length]=d.CLASS_SELECTED;}if(l.sortable){i[i.length]=d.CLASS_SORTABLE;}if(l.resizeable){i[i.length]=d.CLASS_RESIZEABLE;}if(l.editor){i[i.length]=d.CLASS_EDITABLE;}if(k){i=i.concat(k);}return i.join(" ");},_clearTrTemplateEl:function(){this._elTrTemplate=null;},_getTrTemplateEl:function(u,o){if(this._elTrTemplate){return this._elTrTemplate;}else{var q=document,s=q.createElement("tr"),l=q.createElement("td"),k=q.createElement("div");l.appendChild(k);var t=document.createDocumentFragment(),r=this._oColumnSet.keys,n;var p;for(var m=0,j=r.length;m<j;m++){n=l.cloneNode(true);n=this._formatTdEl(r[m],n,m,(m===j-1));t.appendChild(n);}s.appendChild(t);s.className=d.CLASS_REC;this._elTrTemplate=s;return s;}},_formatTdEl:function(n,p,q,m){var t=this._oColumnSet;var i=t.headers,k=i[q],o="",v;for(var l=0,u=k.length;l<u;l++){v=this._sId+"-th-"+k[l]+" ";o+=v;}p.headers=o;var s=[];if(q===0){s[s.length]=d.CLASS_FIRST;}if(m){s[s.length]=d.CLASS_LAST;}p.className=this._getColumnClassNames(n,s);p.firstChild.className=d.CLASS_LINER;if(n.width&&d._bDynStylesFallback){var r=(n.minWidth&&(n.width<n.minWidth))?n.minWidth:n.width;p.firstChild.style.overflow="hidden";p.firstChild.style.width=r+"px";}return p;},_addTrEl:function(k){var j=this._getTrTemplateEl();var i=j.cloneNode(true);return this._updateTrEl(i,k);},_updateTrEl:function(q,r){var p=this.get("formatRow")?this.get("formatRow").call(this,q,r):true;if(p){q.style.display="none";var o=q.childNodes,m;for(var l=0,n=o.length;l<n;++l){m=o[l];this.formatCell(o[l].firstChild,r,this._oColumnSet.keys[l]);}q.style.display="";}var j=q.id,k=r.getId();if(this._sFirstTrId===j){this._sFirstTrId=k;}if(this._sLastTrId===j){this._sLastTrId=k;}q.id=k;return q;},_deleteTrEl:function(i){var j;if(!h.isNumber(i)){j=c.get(i).sectionRowIndex;}else{j=i;}if(h.isNumber(j)&&(j>-2)&&(j<this._elTbody.rows.length)){return this._elTbody.removeChild(this._elTbody.rows[i]);}else{return null;}},_unsetFirstRow:function(){if(this._sFirstTrId){c.removeClass(this._sFirstTrId,d.CLASS_FIRST);this._sFirstTrId=null;}},_setFirstRow:function(){this._unsetFirstRow();var i=this.getFirstTrEl();if(i){c.addClass(i,d.CLASS_FIRST);this._sFirstTrId=i.id;}},_unsetLastRow:function(){if(this._sLastTrId){c.removeClass(this._sLastTrId,d.CLASS_LAST);this._sLastTrId=null;}},_setLastRow:function(){this._unsetLastRow();var i=this.getLastTrEl();if(i){c.addClass(i,d.CLASS_LAST);this._sLastTrId=i.id;}},_setRowStripes:function(t,l){var m=this._elTbody.rows,q=0,s=m.length,p=[],r=0,n=[],j=0;if((t!==null)&&(t!==undefined)){var o=this.getTrEl(t);if(o){q=o.sectionRowIndex;if(h.isNumber(l)&&(l>1)){s=q+l;}}}for(var k=q;k<s;k++){if(k%2){p[r++]=m[k];}else{n[j++]=m[k];}}if(p.length){c.replaceClass(p,d.CLASS_EVEN,d.CLASS_ODD);}if(n.length){c.replaceClass(n,d.CLASS_ODD,d.CLASS_EVEN);}},_setSelections:function(){var l=this.getSelectedRows();var n=this.getSelectedCells();if((l.length>0)||(n.length>0)){var m=this._oColumnSet,k;for(var j=0;j<l.length;j++){k=c.get(l[j]);if(k){c.addClass(k,d.CLASS_SELECTED);}}for(j=0;j<n.length;j++){k=c.get(n[j].recordId);if(k){c.addClass(k.childNodes[m.getColumn(n[j].columnKey).getKeyIndex()],d.CLASS_SELECTED);}}}},_onRenderChainEnd:function(){this.hideTableMessage();if(this._elTbody.rows.length===0){this.showTableMessage(this.get("MSG_EMPTY"),d.CLASS_EMPTY);}var i=this;setTimeout(function(){if((i instanceof d)&&i._sId){if(i._bInit){i._bInit=false;i.fireEvent("initEvent");}i.fireEvent("renderEvent");i.fireEvent("refreshEvent");i.validateColumnWidths();i.fireEvent("postRenderEvent");}},0);},_onDocumentClick:function(l,j){var m=g.getTarget(l);var i=m.nodeName.toLowerCase();if(!c.isAncestor(j._elContainer,m)){j.fireEvent("tableBlurEvent");if(j._oCellEditor){if(j._oCellEditor.getContainerEl){var k=j._oCellEditor.getContainerEl();if(!c.isAncestor(k,m)&&(k.id!==m.id)){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor});}}else{if(j._oCellEditor.isActive){if(!c.isAncestor(j._oCellEditor.container,m)&&(j._oCellEditor.container.id!==m.id)){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor});}}}}}},_onTableFocus:function(j,i){i.fireEvent("tableFocusEvent");},_onTheadFocus:function(j,i){i.fireEvent("theadFocusEvent");i.fireEvent("tableFocusEvent");},_onTbodyFocus:function(j,i){i.fireEvent("tbodyFocusEvent");i.fireEvent("tableFocusEvent");},_onTableMouseover:function(n,m,i,k){var o=m;var j=o.nodeName&&o.nodeName.toLowerCase();var l=true;while(o&&(j!="table")){switch(j){case"body":return;case"a":break;case"td":l=k.fireEvent("cellMouseoverEvent",{target:o,event:n});break;case"span":if(c.hasClass(o,d.CLASS_LABEL)){l=k.fireEvent("theadLabelMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerLabelMouseoverEvent",{target:o,event:n});}break;case"th":l=k.fireEvent("theadCellMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerCellMouseoverEvent",{target:o,event:n});break;case"tr":if(o.parentNode.nodeName.toLowerCase()=="thead"){l=k.fireEvent("theadRowMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerRowMouseoverEvent",{target:o,event:n});}else{l=k.fireEvent("rowMouseoverEvent",{target:o,event:n});}break;default:break;}if(l===false){return;}else{o=o.parentNode;if(o){j=o.nodeName.toLowerCase();}}}k.fireEvent("tableMouseoverEvent",{target:(o||k._elContainer),event:n});},_onTableMouseout:function(n,m,i,k){var o=m;var j=o.nodeName&&o.nodeName.toLowerCase();var l=true;while(o&&(j!="table")){switch(j){case"body":return;case"a":break;case"td":l=k.fireEvent("cellMouseoutEvent",{target:o,event:n});break;case"span":if(c.hasClass(o,d.CLASS_LABEL)){l=k.fireEvent("theadLabelMouseoutEvent",{target:o,event:n});l=k.fireEvent("headerLabelMouseoutEvent",{target:o,event:n});}break;case"th":l=k.fireEvent("theadCellMouseoutEvent",{target:o,event:n});l=k.fireEvent("headerCellMouseoutEvent",{target:o,event:n});break;case"tr":if(o.parentNode.nodeName.toLowerCase()=="thead"){l=k.fireEvent("theadRowMouseoutEvent",{target:o,event:n});
+l=k.fireEvent("headerRowMouseoutEvent",{target:o,event:n});}else{l=k.fireEvent("rowMouseoutEvent",{target:o,event:n});}break;default:break;}if(l===false){return;}else{o=o.parentNode;if(o){j=o.nodeName.toLowerCase();}}}k.fireEvent("tableMouseoutEvent",{target:(o||k._elContainer),event:n});},_onTableMousedown:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"a":break;case"td":k=j.fireEvent("cellMousedownEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelMousedownEvent",{target:m,event:l});k=j.fireEvent("headerLabelMousedownEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellMousedownEvent",{target:m,event:l});k=j.fireEvent("headerCellMousedownEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowMousedownEvent",{target:m,event:l});k=j.fireEvent("headerRowMousedownEvent",{target:m,event:l});}else{k=j.fireEvent("rowMousedownEvent",{target:m,event:l});}break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableMousedownEvent",{target:(m||j._elContainer),event:l});},_onTableMouseup:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"a":break;case"td":k=j.fireEvent("cellMouseupEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelMouseupEvent",{target:m,event:l});k=j.fireEvent("headerLabelMouseupEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellMouseupEvent",{target:m,event:l});k=j.fireEvent("headerCellMouseupEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowMouseupEvent",{target:m,event:l});k=j.fireEvent("headerRowMouseupEvent",{target:m,event:l});}else{k=j.fireEvent("rowMouseupEvent",{target:m,event:l});}break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableMouseupEvent",{target:(m||j._elContainer),event:l});},_onTableDblclick:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"td":k=j.fireEvent("cellDblclickEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelDblclickEvent",{target:m,event:l});k=j.fireEvent("headerLabelDblclickEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellDblclickEvent",{target:m,event:l});k=j.fireEvent("headerCellDblclickEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowDblclickEvent",{target:m,event:l});k=j.fireEvent("headerRowDblclickEvent",{target:m,event:l});}else{k=j.fireEvent("rowDblclickEvent",{target:m,event:l});}break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableDblclickEvent",{target:(m||j._elContainer),event:l});},_onTheadKeydown:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":case"textarea":break;case"thead":k=j.fireEvent("theadKeyEvent",{target:m,event:l});break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableKeyEvent",{target:(m||j._elContainer),event:l});},_onTbodyKeydown:function(m,k){var j=k.get("selectionMode");if(j=="standard"){k._handleStandardSelectionByKey(m);}else{if(j=="single"){k._handleSingleSelectionByKey(m);}else{if(j=="cellblock"){k._handleCellBlockSelectionByKey(m);}else{if(j=="cellrange"){k._handleCellRangeSelectionByKey(m);}else{if(j=="singlecell"){k._handleSingleCellSelectionByKey(m);}}}}}if(k._oCellEditor){if(k._oCellEditor.fireEvent){k._oCellEditor.fireEvent("blurEvent",{editor:k._oCellEditor});}else{if(k._oCellEditor.isActive){k.fireEvent("editorBlurEvent",{editor:k._oCellEditor});}}}var n=g.getTarget(m);var i=n.nodeName&&n.nodeName.toLowerCase();var l=true;while(n&&(i!="table")){switch(i){case"body":return;case"tbody":l=k.fireEvent("tbodyKeyEvent",{target:n,event:m});break;default:break;}if(l===false){return;}else{n=n.parentNode;if(n){i=n.nodeName.toLowerCase();}}}k.fireEvent("tableKeyEvent",{target:(n||k._elContainer),event:m});},_onTheadClick:function(l,j){if(j._oCellEditor){if(j._oCellEditor.fireEvent){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor});}else{if(j._oCellEditor.isActive){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor});}}}var m=g.getTarget(l),i=m.nodeName&&m.nodeName.toLowerCase(),k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":var n=m.type.toLowerCase();if(n=="checkbox"){k=j.fireEvent("theadCheckboxClickEvent",{target:m,event:l});}else{if(n=="radio"){k=j.fireEvent("theadRadioClickEvent",{target:m,event:l});}else{if((n=="button")||(n=="image")||(n=="submit")||(n=="reset")){if(!m.disabled){k=j.fireEvent("theadButtonClickEvent",{target:m,event:l});}else{k=false;}}else{if(m.disabled){k=false;}}}}break;case"a":k=j.fireEvent("theadLinkClickEvent",{target:m,event:l});break;case"button":if(!m.disabled){k=j.fireEvent("theadButtonClickEvent",{target:m,event:l});}else{k=false;}break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelClickEvent",{target:m,event:l});k=j.fireEvent("headerLabelClickEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellClickEvent",{target:m,event:l});k=j.fireEvent("headerCellClickEvent",{target:m,event:l});break;case"tr":k=j.fireEvent("theadRowClickEvent",{target:m,event:l});k=j.fireEvent("headerRowClickEvent",{target:m,event:l});break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableClickEvent",{target:(m||j._elContainer),event:l});},_onTbodyClick:function(l,j){if(j._oCellEditor){if(j._oCellEditor.fireEvent){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor});
+}else{if(j._oCellEditor.isActive){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor});}}}var m=g.getTarget(l),i=m.nodeName&&m.nodeName.toLowerCase(),k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":var n=m.type.toLowerCase();if(n=="checkbox"){k=j.fireEvent("checkboxClickEvent",{target:m,event:l});}else{if(n=="radio"){k=j.fireEvent("radioClickEvent",{target:m,event:l});}else{if((n=="button")||(n=="image")||(n=="submit")||(n=="reset")){if(!m.disabled){k=j.fireEvent("buttonClickEvent",{target:m,event:l});}else{k=false;}}else{if(m.disabled){k=false;}}}}break;case"a":k=j.fireEvent("linkClickEvent",{target:m,event:l});break;case"button":if(!m.disabled){k=j.fireEvent("buttonClickEvent",{target:m,event:l});}else{k=false;}break;case"td":k=j.fireEvent("cellClickEvent",{target:m,event:l});break;case"tr":k=j.fireEvent("rowClickEvent",{target:m,event:l});break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableClickEvent",{target:(m||j._elContainer),event:l});},_onDropdownChange:function(j,i){var k=g.getTarget(j);i.fireEvent("dropdownChangeEvent",{event:j,target:k});},configs:null,getId:function(){return this._sId;},toString:function(){return"DataTable instance "+this._sId;},getDataSource:function(){return this._oDataSource;},getColumnSet:function(){return this._oColumnSet;},getRecordSet:function(){return this._oRecordSet;},getState:function(){return{totalRecords:this.get("paginator")?this.get("paginator").get("totalRecords"):this._oRecordSet.getLength(),pagination:this.get("paginator")?this.get("paginator").getState():null,sortedBy:this.get("sortedBy"),selectedRows:this.getSelectedRows(),selectedCells:this.getSelectedCells()};},getContainerEl:function(){return this._elContainer;},getTableEl:function(){return this._elTable;},getTheadEl:function(){return this._elThead;},getTbodyEl:function(){return this._elTbody;},getMsgTbodyEl:function(){return this._elMsgTbody;},getMsgTdEl:function(){return this._elMsgTd;},getTrEl:function(k){if(k instanceof YAHOO.widget.Record){return document.getElementById(k.getId());}else{if(h.isNumber(k)){var j=c.getElementsByClassName(d.CLASS_REC,"tr",this._elTbody);return j&&j[k]?j[k]:null;}else{if(k){var i=(h.isString(k))?document.getElementById(k):k;if(i&&i.ownerDocument==document){if(i.nodeName.toLowerCase()!="tr"){i=c.getAncestorByTagName(i,"tr");}return i;}}}}return null;},getFirstTrEl:function(){var k=this._elTbody.rows,j=0;while(k[j]){if(this.getRecord(k[j])){return k[j];}j++;}return null;},getLastTrEl:function(){var k=this._elTbody.rows,j=k.length-1;while(j>-1){if(this.getRecord(k[j])){return k[j];}j--;}return null;},getNextTrEl:function(l,i){var j=this.getTrIndex(l);if(j!==null){var k=this._elTbody.rows;if(i){while(j<k.length-1){l=k[j+1];if(this.getRecord(l)){return l;}j++;}}else{if(j<k.length-1){return k[j+1];}}}return null;},getPreviousTrEl:function(l,i){var j=this.getTrIndex(l);if(j!==null){var k=this._elTbody.rows;if(i){while(j>0){l=k[j-1];if(this.getRecord(l)){return l;}j--;}}else{if(j>0){return k[j-1];}}}return null;},getCellIndex:function(k){k=this.getTdEl(k);if(k){if(b.ie>0){var l=0,n=k.parentNode,m=n.childNodes,j=m.length;for(;l<j;l++){if(m[l]==k){return l;}}}else{return k.cellIndex;}}},getTdLinerEl:function(i){var j=this.getTdEl(i);return j.firstChild||null;},getTdEl:function(i){var n;var l=c.get(i);if(l&&(l.ownerDocument==document)){if(l.nodeName.toLowerCase()!="td"){n=c.getAncestorByTagName(l,"td");}else{n=l;}if(n&&((n.parentNode.parentNode==this._elTbody)||(n.parentNode.parentNode===null)||(n.parentNode.parentNode.nodeType===11))){return n;}}else{if(i){var m,k;if(h.isString(i.columnKey)&&h.isString(i.recordId)){m=this.getRecord(i.recordId);var o=this.getColumn(i.columnKey);if(o){k=o.getKeyIndex();}}if(i.record&&i.column&&i.column.getKeyIndex){m=i.record;k=i.column.getKeyIndex();}var j=this.getTrEl(m);if((k!==null)&&j&&j.cells&&j.cells.length>0){return j.cells[k]||null;}}}return null;},getFirstTdEl:function(j){var i=h.isValue(j)?this.getTrEl(j):this.getFirstTrEl();if(i){if(i.cells&&i.cells.length>0){return i.cells[0];}else{if(i.childNodes&&i.childNodes.length>0){return i.childNodes[0];}}}return null;},getLastTdEl:function(j){var i=h.isValue(j)?this.getTrEl(j):this.getLastTrEl();if(i){if(i.cells&&i.cells.length>0){return i.cells[i.cells.length-1];}else{if(i.childNodes&&i.childNodes.length>0){return i.childNodes[i.childNodes.length-1];}}}return null;},getNextTdEl:function(i){var m=this.getTdEl(i);if(m){var k=this.getCellIndex(m);var j=this.getTrEl(m);if(j.cells&&(j.cells.length)>0&&(k<j.cells.length-1)){return j.cells[k+1];}else{if(j.childNodes&&(j.childNodes.length)>0&&(k<j.childNodes.length-1)){return j.childNodes[k+1];}else{var l=this.getNextTrEl(j);if(l){return l.cells[0];}}}}return null;},getPreviousTdEl:function(i){var m=this.getTdEl(i);if(m){var k=this.getCellIndex(m);var j=this.getTrEl(m);if(k>0){if(j.cells&&j.cells.length>0){return j.cells[k-1];}else{if(j.childNodes&&j.childNodes.length>0){return j.childNodes[k-1];}}}else{var l=this.getPreviousTrEl(j);if(l){return this.getLastTdEl(l);}}}return null;},getAboveTdEl:function(j,i){var m=this.getTdEl(j);if(m){var l=this.getPreviousTrEl(m,i);if(l){var k=this.getCellIndex(m);if(l.cells&&l.cells.length>0){return l.cells[k]?l.cells[k]:null;}else{if(l.childNodes&&l.childNodes.length>0){return l.childNodes[k]?l.childNodes[k]:null;}}}}return null;},getBelowTdEl:function(j,i){var m=this.getTdEl(j);if(m){var l=this.getNextTrEl(m,i);if(l){var k=this.getCellIndex(m);if(l.cells&&l.cells.length>0){return l.cells[k]?l.cells[k]:null;}else{if(l.childNodes&&l.childNodes.length>0){return l.childNodes[k]?l.childNodes[k]:null;}}}}return null;},getThLinerEl:function(j){var i=this.getColumn(j);return(i)?i.getThLinerEl():null;},getThEl:function(k){var l;if(k instanceof YAHOO.widget.Column){var j=k;l=j.getThEl();if(l){return l;}}else{var i=c.get(k);if(i&&(i.ownerDocument==document)){if(i.nodeName.toLowerCase()!="th"){l=c.getAncestorByTagName(i,"th");
+}else{l=i;}return l;}}return null;},getTrIndex:function(m){var i=this.getRecord(m),k=this.getRecordIndex(i),l;if(i){l=this.getTrEl(i);if(l){return l.sectionRowIndex;}else{var j=this.get("paginator");if(j){return j.get("recordOffset")+k;}else{return k;}}}return null;},load:function(i){i=i||{};(i.datasource||this._oDataSource).sendRequest(i.request||this.get("initialRequest"),i.callback||{success:this.onDataReturnInitializeTable,failure:this.onDataReturnInitializeTable,scope:this,argument:this.getState()});},initializeTable:function(){this._bInit=true;this._oRecordSet.reset();var i=this.get("paginator");if(i){i.set("totalRecords",0);}this._unselectAllTrEls();this._unselectAllTdEls();this._aSelections=null;this._oAnchorRecord=null;this._oAnchorCell=null;this.set("sortedBy",null);},_runRenderChain:function(){this._oChainRender.run();},_getViewRecords:function(){var i=this.get("paginator");if(i){return this._oRecordSet.getRecords(i.getStartIndex(),i.getRowsPerPage());}else{return this._oRecordSet.getRecords();}},render:function(){this._oChainRender.stop();this.fireEvent("beforeRenderEvent");var r,p,o,s,l=this._getViewRecords();var m=this._elTbody,q=this.get("renderLoopSize"),t=l.length;if(t>0){m.style.display="none";while(m.lastChild){m.removeChild(m.lastChild);}m.style.display="";this._oChainRender.add({method:function(u){if((this instanceof d)&&this._sId){var k=u.nCurrentRecord,w=((u.nCurrentRecord+u.nLoopLength)>t)?t:(u.nCurrentRecord+u.nLoopLength),j,v;m.style.display="none";for(;k<w;k++){j=c.get(l[k].getId());j=j||this._addTrEl(l[k]);v=m.childNodes[k]||null;m.insertBefore(j,v);}m.style.display="";u.nCurrentRecord=k;}},scope:this,iterations:(q>0)?Math.ceil(t/q):1,argument:{nCurrentRecord:0,nLoopLength:(q>0)?q:t},timeout:(q>0)?0:-1});this._oChainRender.add({method:function(i){if((this instanceof d)&&this._sId){while(m.rows.length>t){m.removeChild(m.lastChild);}this._setFirstRow();this._setLastRow();this._setRowStripes();this._setSelections();}},scope:this,timeout:(q>0)?0:-1});}else{var n=m.rows.length;if(n>0){this._oChainRender.add({method:function(k){if((this instanceof d)&&this._sId){var j=k.nCurrent,v=k.nLoopLength,u=(j-v<0)?0:j-v;m.style.display="none";for(;j>u;j--){m.deleteRow(-1);}m.style.display="";k.nCurrent=j;}},scope:this,iterations:(q>0)?Math.ceil(n/q):1,argument:{nCurrent:n,nLoopLength:(q>0)?q:n},timeout:(q>0)?0:-1});}}this._runRenderChain();},disable:function(){this._disabled=true;var i=this._elTable;var j=this._elMask;j.style.width=i.offsetWidth+"px";j.style.height=i.offsetHeight+"px";j.style.left=i.offsetLeft+"px";j.style.display="";this.fireEvent("disableEvent");},undisable:function(){this._disabled=false;this._elMask.style.display="none";this.fireEvent("undisableEvent");},isDisabled:function(){return this._disabled;},destroy:function(){var k=this.toString();this._oChainRender.stop();this._destroyColumnHelpers();var m;for(var l=0,j=this._oColumnSet.flat.length;l<j;l++){m=this._oColumnSet.flat[l].editor;if(m&&m.destroy){m.destroy();this._oColumnSet.flat[l].editor=null;}}this._destroyPaginator();this._oRecordSet.unsubscribeAll();this.unsubscribeAll();g.removeListener(document,"click",this._onDocumentClick);this._destroyContainerEl(this._elContainer);for(var n in this){if(h.hasOwnProperty(this,n)){this[n]=null;}}d._nCurrentCount--;if(d._nCurrentCount<1){if(d._elDynStyleNode){document.getElementsByTagName("head")[0].removeChild(d._elDynStyleNode);d._elDynStyleNode=null;}}},showTableMessage:function(j,i){var k=this._elMsgTd;if(h.isString(j)){k.firstChild.innerHTML=j;}if(h.isString(i)){k.className=i;}this._elMsgTbody.style.display="";this.fireEvent("tableMsgShowEvent",{html:j,className:i});},hideTableMessage:function(){if(this._elMsgTbody.style.display!="none"){this._elMsgTbody.style.display="none";this._elMsgTbody.parentNode.style.width="";this.fireEvent("tableMsgHideEvent");}},focus:function(){this.focusTbodyEl();},focusTheadEl:function(){this._focusEl(this._elThead);},focusTbodyEl:function(){this._focusEl(this._elTbody);},onShow:function(){this.validateColumnWidths();for(var m=this._oColumnSet.keys,l=0,j=m.length,k;l<j;l++){k=m[l];if(k._ddResizer){k._ddResizer.resetResizerEl();}}},getRecordIndex:function(l){var k;if(!h.isNumber(l)){if(l instanceof YAHOO.widget.Record){return this._oRecordSet.getRecordIndex(l);}else{var j=this.getTrEl(l);if(j){k=j.sectionRowIndex;}}}else{k=l;}if(h.isNumber(k)){var i=this.get("paginator");if(i){return i.get("recordOffset")+k;}else{return k;}}return null;},getRecord:function(k){var j=this._oRecordSet.getRecord(k);if(!j){var i=this.getTrEl(k);if(i){j=this._oRecordSet.getRecord(i.id);}}if(j instanceof YAHOO.widget.Record){return this._oRecordSet.getRecord(j);}else{return null;}},getColumn:function(m){var o=this._oColumnSet.getColumn(m);if(!o){var n=this.getTdEl(m);if(n){o=this._oColumnSet.getColumn(this.getCellIndex(n));}else{n=this.getThEl(m);if(n){var k=this._oColumnSet.flat;for(var l=0,j=k.length;l<j;l++){if(k[l].getThEl().id===n.id){o=k[l];}}}}}if(!o){}return o;},getColumnById:function(i){return this._oColumnSet.getColumnById(i);},getColumnSortDir:function(k,l){if(k.sortOptions&&k.sortOptions.defaultDir){if(k.sortOptions.defaultDir=="asc"){k.sortOptions.defaultDir=d.CLASS_ASC;}else{if(k.sortOptions.defaultDir=="desc"){k.sortOptions.defaultDir=d.CLASS_DESC;}}}var j=(k.sortOptions&&k.sortOptions.defaultDir)?k.sortOptions.defaultDir:d.CLASS_ASC;var i=false;l=l||this.get("sortedBy");if(l&&(l.key===k.key)){i=true;if(l.dir){j=(l.dir===d.CLASS_ASC)?d.CLASS_DESC:d.CLASS_ASC;}else{j=(j===d.CLASS_ASC)?d.CLASS_DESC:d.CLASS_ASC;}}return j;},doBeforeSortColumn:function(j,i){this.showTableMessage(this.get("MSG_LOADING"),d.CLASS_LOADING);return true;},sortColumn:function(m,j){if(m&&(m instanceof YAHOO.widget.Column)){if(!m.sortable){c.addClass(this.getThEl(m),d.CLASS_SORTABLE);}if(j&&(j!==d.CLASS_ASC)&&(j!==d.CLASS_DESC)){j=null;}var n=j||this.getColumnSortDir(m);var l=this.get("sortedBy")||{};var t=(l.key===m.key)?true:false;var p=this.doBeforeSortColumn(m,n);
+if(p){if(this.get("dynamicData")){var s=this.getState();if(s.pagination){s.pagination.recordOffset=0;}s.sortedBy={key:m.key,dir:n};var k=this.get("generateRequest")(s,this);this.unselectAllRows();this.unselectAllCells();var r={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,argument:s,scope:this};this._oDataSource.sendRequest(k,r);}else{var i=(m.sortOptions&&h.isFunction(m.sortOptions.sortFunction))?m.sortOptions.sortFunction:null;if(!t||j||i){i=i||this.get("sortFunction");var q=(m.sortOptions&&m.sortOptions.field)?m.sortOptions.field:m.field;this._oRecordSet.sortRecords(i,((n==d.CLASS_DESC)?true:false),q);}else{this._oRecordSet.reverseRecords();}var o=this.get("paginator");if(o){o.setPage(1,true);}this.render();this.set("sortedBy",{key:m.key,dir:n,column:m});}this.fireEvent("columnSortEvent",{column:m,dir:n});return;}}},setColumnWidth:function(j,i){if(!(j instanceof YAHOO.widget.Column)){j=this.getColumn(j);}if(j){if(h.isNumber(i)){i=(i>j.minWidth)?i:j.minWidth;j.width=i;this._setColumnWidth(j,i+"px");this.fireEvent("columnSetWidthEvent",{column:j,width:i});}else{if(i===null){j.width=i;this._setColumnWidth(j,"auto");this.validateColumnWidths(j);this.fireEvent("columnUnsetWidthEvent",{column:j});}}this._clearTrTemplateEl();}else{}},_setColumnWidth:function(j,i,k){if(j&&(j.getKeyIndex()!==null)){k=k||(((i==="")||(i==="auto"))?"visible":"hidden");if(!d._bDynStylesFallback){this._setColumnWidthDynStyles(j,i,k);}else{this._setColumnWidthDynFunction(j,i,k);}}else{}},_setColumnWidthDynStyles:function(m,l,n){var j=d._elDynStyleNode,k;if(!j){j=document.createElement("style");j.type="text/css";j=document.getElementsByTagName("head").item(0).appendChild(j);d._elDynStyleNode=j;}if(j){var i="."+this.getId()+"-col-"+m.getSanitizedKey()+" ."+d.CLASS_LINER;if(this._elTbody){this._elTbody.style.display="none";}k=d._oDynStyles[i];if(!k){if(j.styleSheet&&j.styleSheet.addRule){j.styleSheet.addRule(i,"overflow:"+n);j.styleSheet.addRule(i,"width:"+l);k=j.styleSheet.rules[j.styleSheet.rules.length-1];d._oDynStyles[i]=k;}else{if(j.sheet&&j.sheet.insertRule){j.sheet.insertRule(i+" {overflow:"+n+";width:"+l+";}",j.sheet.cssRules.length);k=j.sheet.cssRules[j.sheet.cssRules.length-1];d._oDynStyles[i]=k;}}}else{k.style.overflow=n;k.style.width=l;}if(this._elTbody){this._elTbody.style.display="";}}if(!k){d._bDynStylesFallback=true;this._setColumnWidthDynFunction(m,l);}},_setColumnWidthDynFunction:function(r,m,s){if(m=="auto"){m="";}var l=this._elTbody?this._elTbody.rows.length:0;if(!this._aDynFunctions[l]){var q,p,o;var t=["var colIdx=oColumn.getKeyIndex();","oColumn.getThLinerEl().style.overflow="];for(q=l-1,p=2;q>=0;--q){t[p++]="this._elTbody.rows[";t[p++]=q;t[p++]="].cells[colIdx].firstChild.style.overflow=";}t[p]="sOverflow;";t[p+1]="oColumn.getThLinerEl().style.width=";for(q=l-1,o=p+2;q>=0;--q){t[o++]="this._elTbody.rows[";t[o++]=q;t[o++]="].cells[colIdx].firstChild.style.width=";}t[o]="sWidth;";this._aDynFunctions[l]=new Function("oColumn","sWidth","sOverflow",t.join(""));}var n=this._aDynFunctions[l];if(n){n.call(this,r,m,s);}},validateColumnWidths:function(o){var l=this._elColgroup;var q=l.cloneNode(true);var p=false;var n=this._oColumnSet.keys;var k;if(o&&!o.hidden&&!o.width&&(o.getKeyIndex()!==null)){k=o.getThLinerEl();if((o.minWidth>0)&&(k.offsetWidth<o.minWidth)){q.childNodes[o.getKeyIndex()].style.width=o.minWidth+(parseInt(c.getStyle(k,"paddingLeft"),10)|0)+(parseInt(c.getStyle(k,"paddingRight"),10)|0)+"px";p=true;}else{if((o.maxAutoWidth>0)&&(k.offsetWidth>o.maxAutoWidth)){this._setColumnWidth(o,o.maxAutoWidth+"px","hidden");}}}else{for(var m=0,j=n.length;m<j;m++){o=n[m];if(!o.hidden&&!o.width){k=o.getThLinerEl();if((o.minWidth>0)&&(k.offsetWidth<o.minWidth)){q.childNodes[m].style.width=o.minWidth+(parseInt(c.getStyle(k,"paddingLeft"),10)|0)+(parseInt(c.getStyle(k,"paddingRight"),10)|0)+"px";p=true;}else{if((o.maxAutoWidth>0)&&(k.offsetWidth>o.maxAutoWidth)){this._setColumnWidth(o,o.maxAutoWidth+"px","hidden");}}}}}if(p){l.parentNode.replaceChild(q,l);this._elColgroup=q;}},_clearMinWidth:function(i){if(i.getKeyIndex()!==null){this._elColgroup.childNodes[i.getKeyIndex()].style.width="";}},_restoreMinWidth:function(i){if(i.minWidth&&(i.getKeyIndex()!==null)){this._elColgroup.childNodes[i.getKeyIndex()].style.width=i.minWidth+"px";}},hideColumn:function(r){if(!(r instanceof YAHOO.widget.Column)){r=this.getColumn(r);}if(r&&!r.hidden&&r.getTreeIndex()!==null){var o=this.getTbodyEl().rows;var n=o.length;var m=this._oColumnSet.getDescendants(r);for(var q=0,s=m.length;q<s;q++){var t=m[q];t.hidden=true;c.addClass(t.getThEl(),d.CLASS_HIDDEN);var k=t.getKeyIndex();if(k!==null){this._clearMinWidth(r);for(var p=0;p<n;p++){c.addClass(o[p].cells[k],d.CLASS_HIDDEN);}}this.fireEvent("columnHideEvent",{column:t});}this._repaintOpera();this._clearTrTemplateEl();}else{}},showColumn:function(r){if(!(r instanceof YAHOO.widget.Column)){r=this.getColumn(r);}if(r&&r.hidden&&(r.getTreeIndex()!==null)){var o=this.getTbodyEl().rows;var n=o.length;var m=this._oColumnSet.getDescendants(r);for(var q=0,s=m.length;q<s;q++){var t=m[q];t.hidden=false;c.removeClass(t.getThEl(),d.CLASS_HIDDEN);var k=t.getKeyIndex();if(k!==null){this._restoreMinWidth(r);for(var p=0;p<n;p++){c.removeClass(o[p].cells[k],d.CLASS_HIDDEN);}}this.fireEvent("columnShowEvent",{column:t});}this._clearTrTemplateEl();}else{}},removeColumn:function(p){if(!(p instanceof YAHOO.widget.Column)){p=this.getColumn(p);}if(p){var m=p.getTreeIndex();if(m!==null){var o,r,q=p.getKeyIndex();if(q===null){var u=[];var j=this._oColumnSet.getDescendants(p);for(o=0,r=j.length;o<r;o++){var s=j[o].getKeyIndex();if(s!==null){u[u.length]=s;}}if(u.length>0){q=u;}}else{q=[q];}if(q!==null){q.sort(function(v,i){return YAHOO.util.Sort.compare(v,i);});this._destroyTheadEl();var k=this._oColumnSet.getDefinitions();p=k.splice(m,1)[0];this._initColumnSet(k);this._initTheadEl();for(o=q.length-1;o>-1;o--){this._removeColgroupColEl(q[o]);}var t=this._elTbody.rows;if(t.length>0){var n=this.get("renderLoopSize"),l=t.length;
+this._oChainRender.add({method:function(y){if((this instanceof d)&&this._sId){var x=y.nCurrentRow,v=n>0?Math.min(x+n,t.length):t.length,z=y.aIndexes,w;for(;x<v;++x){for(w=z.length-1;w>-1;w--){t[x].removeChild(t[x].childNodes[z[w]]);}}y.nCurrentRow=x;}},iterations:(n>0)?Math.ceil(l/n):1,argument:{nCurrentRow:0,aIndexes:q},scope:this,timeout:(n>0)?0:-1});this._runRenderChain();}this.fireEvent("columnRemoveEvent",{column:p});return p;}}}},insertColumn:function(r,s){if(r instanceof YAHOO.widget.Column){r=r.getDefinition();}else{if(r.constructor!==Object){return;}}var x=this._oColumnSet;if(!h.isValue(s)||!h.isNumber(s)){s=x.tree[0].length;}this._destroyTheadEl();var z=this._oColumnSet.getDefinitions();z.splice(s,0,r);this._initColumnSet(z);this._initTheadEl();x=this._oColumnSet;var n=x.tree[0][s];var p,t,w=[];var l=x.getDescendants(n);for(p=0,t=l.length;p<t;p++){var u=l[p].getKeyIndex();if(u!==null){w[w.length]=u;}}if(w.length>0){var y=w.sort(function(A,i){return YAHOO.util.Sort.compare(A,i);})[0];for(p=w.length-1;p>-1;p--){this._insertColgroupColEl(w[p]);}var v=this._elTbody.rows;if(v.length>0){var o=this.get("renderLoopSize"),m=v.length;var k=[],q;for(p=0,t=w.length;p<t;p++){var j=w[p];q=this._getTrTemplateEl().childNodes[p].cloneNode(true);q=this._formatTdEl(this._oColumnSet.keys[j],q,j,(j===this._oColumnSet.keys.length-1));k[j]=q;}this._oChainRender.add({method:function(D){if((this instanceof d)&&this._sId){var C=D.nCurrentRow,B,F=D.descKeyIndexes,A=o>0?Math.min(C+o,v.length):v.length,E;for(;C<A;++C){E=v[C].childNodes[y]||null;for(B=F.length-1;B>-1;B--){v[C].insertBefore(D.aTdTemplates[F[B]].cloneNode(true),E);}}D.nCurrentRow=C;}},iterations:(o>0)?Math.ceil(m/o):1,argument:{nCurrentRow:0,aTdTemplates:k,descKeyIndexes:w},scope:this,timeout:(o>0)?0:-1});this._runRenderChain();}this.fireEvent("columnInsertEvent",{column:r,index:s});return n;}},reorderColumn:function(q,r){if(!(q instanceof YAHOO.widget.Column)){q=this.getColumn(q);}if(q&&YAHOO.lang.isNumber(r)){var z=q.getTreeIndex();if((z!==null)&&(z!==r)){var p,s,l=q.getKeyIndex(),k,v=[],t;if(l===null){k=this._oColumnSet.getDescendants(q);for(p=0,s=k.length;p<s;p++){t=k[p].getKeyIndex();if(t!==null){v[v.length]=t;}}if(v.length>0){l=v;}}else{l=[l];}if(l!==null){l.sort(function(A,i){return YAHOO.util.Sort.compare(A,i);});this._destroyTheadEl();var w=this._oColumnSet.getDefinitions();var j=w.splice(z,1)[0];w.splice(r,0,j);this._initColumnSet(w);this._initTheadEl();var n=this._oColumnSet.tree[0][r];var y=n.getKeyIndex();if(y===null){v=[];k=this._oColumnSet.getDescendants(n);for(p=0,s=k.length;p<s;p++){t=k[p].getKeyIndex();if(t!==null){v[v.length]=t;}}if(v.length>0){y=v;}}else{y=[y];}var x=y.sort(function(A,i){return YAHOO.util.Sort.compare(A,i);})[0];this._reorderColgroupColEl(l,x);var u=this._elTbody.rows;if(u.length>0){var o=this.get("renderLoopSize"),m=u.length;this._oChainRender.add({method:function(D){if((this instanceof d)&&this._sId){var C=D.nCurrentRow,B,F,E,A=o>0?Math.min(C+o,u.length):u.length,H=D.aIndexes,G;for(;C<A;++C){F=[];G=u[C];for(B=H.length-1;B>-1;B--){F.push(G.removeChild(G.childNodes[H[B]]));}E=G.childNodes[x]||null;for(B=F.length-1;B>-1;B--){G.insertBefore(F[B],E);}}D.nCurrentRow=C;}},iterations:(o>0)?Math.ceil(m/o):1,argument:{nCurrentRow:0,aIndexes:l},scope:this,timeout:(o>0)?0:-1});this._runRenderChain();}this.fireEvent("columnReorderEvent",{column:n,oldIndex:z});return n;}}}},selectColumn:function(k){k=this.getColumn(k);if(k&&!k.selected){if(k.getKeyIndex()!==null){k.selected=true;var l=k.getThEl();c.addClass(l,d.CLASS_SELECTED);var j=this.getTbodyEl().rows;var i=this._oChainRender;i.add({method:function(m){if((this instanceof d)&&this._sId&&j[m.rowIndex]&&j[m.rowIndex].cells[m.cellIndex]){c.addClass(j[m.rowIndex].cells[m.cellIndex],d.CLASS_SELECTED);}m.rowIndex++;},scope:this,iterations:j.length,argument:{rowIndex:0,cellIndex:k.getKeyIndex()}});this._clearTrTemplateEl();this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnSelectEvent",{column:k});}else{}}},unselectColumn:function(k){k=this.getColumn(k);if(k&&k.selected){if(k.getKeyIndex()!==null){k.selected=false;var l=k.getThEl();c.removeClass(l,d.CLASS_SELECTED);var j=this.getTbodyEl().rows;var i=this._oChainRender;i.add({method:function(m){if((this instanceof d)&&this._sId&&j[m.rowIndex]&&j[m.rowIndex].cells[m.cellIndex]){c.removeClass(j[m.rowIndex].cells[m.cellIndex],d.CLASS_SELECTED);}m.rowIndex++;},scope:this,iterations:j.length,argument:{rowIndex:0,cellIndex:k.getKeyIndex()}});this._clearTrTemplateEl();this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnUnselectEvent",{column:k});}else{}}},getSelectedColumns:function(n){var k=[];var l=this._oColumnSet.keys;for(var m=0,j=l.length;m<j;m++){if(l[m].selected){k[k.length]=l[m];}}return k;},highlightColumn:function(i){var l=this.getColumn(i);if(l&&(l.getKeyIndex()!==null)){var m=l.getThEl();c.addClass(m,d.CLASS_HIGHLIGHTED);var k=this.getTbodyEl().rows;var j=this._oChainRender;j.add({method:function(n){if((this instanceof d)&&this._sId&&k[n.rowIndex]&&k[n.rowIndex].cells[n.cellIndex]){c.addClass(k[n.rowIndex].cells[n.cellIndex],d.CLASS_HIGHLIGHTED);}n.rowIndex++;},scope:this,iterations:k.length,argument:{rowIndex:0,cellIndex:l.getKeyIndex()},timeout:-1});this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnHighlightEvent",{column:l});}else{}},unhighlightColumn:function(i){var l=this.getColumn(i);if(l&&(l.getKeyIndex()!==null)){var m=l.getThEl();c.removeClass(m,d.CLASS_HIGHLIGHTED);var k=this.getTbodyEl().rows;var j=this._oChainRender;j.add({method:function(n){if((this instanceof d)&&this._sId&&k[n.rowIndex]&&k[n.rowIndex].cells[n.cellIndex]){c.removeClass(k[n.rowIndex].cells[n.cellIndex],d.CLASS_HIGHLIGHTED);}n.rowIndex++;},scope:this,iterations:k.length,argument:{rowIndex:0,cellIndex:l.getKeyIndex()},timeout:-1});this._elTbody.style.display="none";
+this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnUnhighlightEvent",{column:l});}else{}},addRow:function(o,k){if(h.isNumber(k)&&(k<0||k>this._oRecordSet.getLength())){return;}if(o&&h.isObject(o)){var m=this._oRecordSet.addRecord(o,k);if(m){var i;var j=this.get("paginator");if(j){var n=j.get("totalRecords");if(n!==e.Paginator.VALUE_UNLIMITED){j.set("totalRecords",n+1);}i=this.getRecordIndex(m);var l=(j.getPageRecords())[1];if(i<=l){this.render();}this.fireEvent("rowAddEvent",{record:m});return;}else{i=this.getRecordIndex(m);if(h.isNumber(i)){this._oChainRender.add({method:function(r){if((this instanceof d)&&this._sId){var s=r.record;var p=r.recIndex;var t=this._addTrEl(s);if(t){var q=(this._elTbody.rows[p])?this._elTbody.rows[p]:null;this._elTbody.insertBefore(t,q);if(p===0){this._setFirstRow();}if(q===null){this._setLastRow();}this._setRowStripes();this.hideTableMessage();this.fireEvent("rowAddEvent",{record:s});}}},argument:{record:m,recIndex:i},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}}},addRows:function(k,n){if(h.isNumber(n)&&(n<0||n>this._oRecordSet.getLength())){return;}if(h.isArray(k)){var o=this._oRecordSet.addRecords(k,n);if(o){var s=this.getRecordIndex(o[0]);var r=this.get("paginator");if(r){var p=r.get("totalRecords");if(p!==e.Paginator.VALUE_UNLIMITED){r.set("totalRecords",p+o.length);}var q=(r.getPageRecords())[1];if(s<=q){this.render();}this.fireEvent("rowsAddEvent",{records:o});return;}else{var m=this.get("renderLoopSize");var j=s+k.length;var i=(j-s);var l=(s>=this._elTbody.rows.length);this._oChainRender.add({method:function(x){if((this instanceof d)&&this._sId){var y=x.aRecords,w=x.nCurrentRow,v=x.nCurrentRecord,t=m>0?Math.min(w+m,j):j,z=document.createDocumentFragment(),u=(this._elTbody.rows[w])?this._elTbody.rows[w]:null;for(;w<t;w++,v++){z.appendChild(this._addTrEl(y[v]));}this._elTbody.insertBefore(z,u);x.nCurrentRow=w;x.nCurrentRecord=v;}},iterations:(m>0)?Math.ceil(j/m):1,argument:{nCurrentRow:s,nCurrentRecord:0,aRecords:o},scope:this,timeout:(m>0)?0:-1});this._oChainRender.add({method:function(u){var t=u.recIndex;if(t===0){this._setFirstRow();}if(u.isLast){this._setLastRow();}this._setRowStripes();this.fireEvent("rowsAddEvent",{records:o});},argument:{recIndex:s,isLast:l},scope:this,timeout:-1});this._runRenderChain();this.hideTableMessage();return;}}}},updateRow:function(u,k){var r=u;if(!h.isNumber(r)){r=this.getRecordIndex(u);}if(h.isNumber(r)&&(r>=0)){var s=this._oRecordSet,q=s.getRecord(r);if(q){var o=this._oRecordSet.setRecord(k,r),j=this.getTrEl(q),p=q?q.getData():null;if(o){var t=this._aSelections||[],n=0,l=q.getId(),m=o.getId();for(;n<t.length;n++){if((t[n]===l)){t[n]=m;}else{if(t[n].recordId===l){t[n].recordId=m;}}}if(this._oAnchorRecord&&this._oAnchorRecord.getId()===l){this._oAnchorRecord=o;}if(this._oAnchorCell&&this._oAnchorCell.record.getId()===l){this._oAnchorCell.record=o;}this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){var v=this.get("paginator");if(v){var i=(v.getPageRecords())[0],w=(v.getPageRecords())[1];if((r>=i)||(r<=w)){this.render();}}else{if(j){this._updateTrEl(j,o);}else{this.getTbodyEl().appendChild(this._addTrEl(o));}}this.fireEvent("rowUpdateEvent",{record:o,oldData:p});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}return;},updateRows:function(A,m){if(h.isArray(m)){var s=A,l=this._oRecordSet,o=l.getLength();if(!h.isNumber(A)){s=this.getRecordIndex(A);}if(h.isNumber(s)&&(s>=0)&&(s<l.getLength())){var E=s+m.length,B=l.getRecords(s,m.length),G=l.setRecords(m,s);if(G){var t=this._aSelections||[],D=0,C,u,x,z,y=this._oAnchorRecord?this._oAnchorRecord.getId():null,n=this._oAnchorCell?this._oAnchorCell.record.getId():null;for(;D<B.length;D++){z=B[D].getId();u=G[D];x=u.getId();for(C=0;C<t.length;C++){if((t[C]===z)){t[C]=x;}else{if(t[C].recordId===z){t[C].recordId=x;}}}if(y&&y===z){this._oAnchorRecord=u;}if(n&&n===z){this._oAnchorCell.record=u;}}var F=this.get("paginator");if(F){var r=(F.getPageRecords())[0],p=(F.getPageRecords())[1];if((s>=r)||(E<=p)){this.render();}this.fireEvent("rowsAddEvent",{newRecords:G,oldRecords:B});return;}else{var k=this.get("renderLoopSize"),v=m.length,w=(E>=o),q=(E>o);this._oChainRender.add({method:function(K){if((this instanceof d)&&this._sId){var L=K.aRecords,J=K.nCurrentRow,I=K.nDataPointer,H=k>0?Math.min(J+k,s+L.length):s+L.length;for(;J<H;J++,I++){if(q&&(J>=o)){this._elTbody.appendChild(this._addTrEl(L[I]));}else{this._updateTrEl(this._elTbody.rows[J],L[I]);}}K.nCurrentRow=J;K.nDataPointer=I;}},iterations:(k>0)?Math.ceil(v/k):1,argument:{nCurrentRow:s,aRecords:G,nDataPointer:0,isAdding:q},scope:this,timeout:(k>0)?0:-1});this._oChainRender.add({method:function(j){var i=j.recIndex;if(i===0){this._setFirstRow();}if(j.isLast){this._setLastRow();}this._setRowStripes();this.fireEvent("rowsAddEvent",{newRecords:G,oldRecords:B});},argument:{recIndex:s,isLast:w},scope:this,timeout:-1});this._runRenderChain();this.hideTableMessage();return;}}}}},deleteRow:function(s){var k=(h.isNumber(s))?s:this.getRecordIndex(s);if(h.isNumber(k)){var t=this.getRecord(k);if(t){var m=this.getTrIndex(k);var p=t.getId();var r=this._aSelections||[];for(var n=r.length-1;n>-1;n--){if((h.isString(r[n])&&(r[n]===p))||(h.isObject(r[n])&&(r[n].recordId===p))){r.splice(n,1);}}var l=this._oRecordSet.deleteRecord(k);if(l){var q=this.get("paginator");if(q){var o=q.get("totalRecords"),i=q.getPageRecords();if(o!==e.Paginator.VALUE_UNLIMITED){q.set("totalRecords",o-1);}if(!i||k<=i[1]){this.render();}this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){this.fireEvent("rowDeleteEvent",{recordIndex:k,oldData:l,trElIndex:m});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();}else{if(h.isNumber(m)){this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){var j=(k===this._oRecordSet.getLength());this._deleteTrEl(m);if(this._elTbody.rows.length>0){if(m===0){this._setFirstRow();
+}if(j){this._setLastRow();}if(m!=this._elTbody.rows.length){this._setRowStripes(m);}}this.fireEvent("rowDeleteEvent",{recordIndex:k,oldData:l,trElIndex:m});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}}}return null;},deleteRows:function(y,s){var l=(h.isNumber(y))?y:this.getRecordIndex(y);if(h.isNumber(l)){var z=this.getRecord(l);if(z){var m=this.getTrIndex(l);var u=z.getId();var x=this._aSelections||[];for(var q=x.length-1;q>-1;q--){if((h.isString(x[q])&&(x[q]===u))||(h.isObject(x[q])&&(x[q].recordId===u))){x.splice(q,1);}}var n=l;var w=l;if(s&&h.isNumber(s)){n=(s>0)?l+s-1:l;w=(s>0)?l:l+s+1;s=(s>0)?s:s*-1;if(w<0){w=0;s=n-w+1;}}else{s=1;}var p=this._oRecordSet.deleteRecords(w,s);if(p){var v=this.get("paginator"),r=this.get("renderLoopSize");if(v){var t=v.get("totalRecords"),k=v.getPageRecords();if(t!==e.Paginator.VALUE_UNLIMITED){v.set("totalRecords",t-p.length);}if(!k||w<=k[1]){this.render();}this._oChainRender.add({method:function(j){if((this instanceof d)&&this._sId){this.fireEvent("rowsDeleteEvent",{recordIndex:w,oldData:p,count:s});}},scope:this,timeout:(r>0)?0:-1});this._runRenderChain();return;}else{if(h.isNumber(m)){var o=w;var i=s;this._oChainRender.add({method:function(B){if((this instanceof d)&&this._sId){var A=B.nCurrentRow,j=(r>0)?(Math.max(A-r,o)-1):o-1;for(;A>j;--A){this._deleteTrEl(A);}B.nCurrentRow=A;}},iterations:(r>0)?Math.ceil(s/r):1,argument:{nCurrentRow:n},scope:this,timeout:(r>0)?0:-1});this._oChainRender.add({method:function(){if(this._elTbody.rows.length>0){this._setFirstRow();this._setLastRow();this._setRowStripes();}this.fireEvent("rowsDeleteEvent",{recordIndex:w,oldData:p,count:s});},scope:this,timeout:-1});this._runRenderChain();return;}}}}}return null;},formatCell:function(j,l,m){if(!l){l=this.getRecord(j);}if(!m){m=this.getColumn(this.getCellIndex(j.parentNode));}if(l&&m){var i=m.field;var n=l.getData(i);var k=typeof m.formatter==="function"?m.formatter:d.Formatter[m.formatter+""]||d.Formatter.defaultFormatter;if(k){k.call(this,j,l,m,n);}else{j.innerHTML=n;}this.fireEvent("cellFormatEvent",{record:l,column:m,key:m.key,el:j});}else{}},updateCell:function(k,m,o,j){m=(m instanceof YAHOO.widget.Column)?m:this.getColumn(m);if(m&&m.getField()&&(k instanceof YAHOO.widget.Record)){var l=m.getField(),n=k.getData(l);this._oRecordSet.updateRecordValue(k,l,o);var i=this.getTdEl({record:k,column:m});if(i){this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){this.formatCell(i.firstChild,k,m);this.fireEvent("cellUpdateEvent",{record:k,column:m,oldData:n});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});if(!j){this._runRenderChain();}}else{this.fireEvent("cellUpdateEvent",{record:k,column:m,oldData:n});}}},_updatePaginator:function(j){var i=this.get("paginator");if(i&&j!==i){i.unsubscribe("changeRequest",this.onPaginatorChangeRequest,this,true);}if(j){j.subscribe("changeRequest",this.onPaginatorChangeRequest,this,true);}},_handlePaginatorChange:function(l){if(l.prevValue===l.newValue){return;}var n=l.newValue,m=l.prevValue,k=this._defaultPaginatorContainers();if(m){if(m.getContainerNodes()[0]==k[0]){m.set("containers",[]);}m.destroy();if(k[0]){if(n&&!n.getContainerNodes().length){n.set("containers",k);}else{for(var j=k.length-1;j>=0;--j){if(k[j]){k[j].parentNode.removeChild(k[j]);}}}}}if(!this._bInit){this.render();}if(n){this.renderPaginator();}},_defaultPaginatorContainers:function(l){var j=this._sId+"-paginator0",k=this._sId+"-paginator1",i=c.get(j),m=c.get(k);if(l&&(!i||!m)){if(!i){i=document.createElement("div");i.id=j;c.addClass(i,d.CLASS_PAGINATOR);this._elContainer.insertBefore(i,this._elContainer.firstChild);}if(!m){m=document.createElement("div");m.id=k;c.addClass(m,d.CLASS_PAGINATOR);this._elContainer.appendChild(m);}}return[i,m];},_destroyPaginator:function(){var i=this.get("paginator");if(i){i.destroy();}},renderPaginator:function(){var i=this.get("paginator");if(!i){return;}if(!i.getContainerNodes().length){i.set("containers",this._defaultPaginatorContainers(true));}i.render();},doBeforePaginatorChange:function(i){this.showTableMessage(this.get("MSG_LOADING"),d.CLASS_LOADING);return true;},onPaginatorChangeRequest:function(l){var j=this.doBeforePaginatorChange(l);if(j){if(this.get("dynamicData")){var i=this.getState();i.pagination=l;var k=this.get("generateRequest")(i,this);this.unselectAllRows();this.unselectAllCells();var m={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,argument:i,scope:this};this._oDataSource.sendRequest(k,m);}else{l.paginator.setStartIndex(l.recordOffset,true);l.paginator.setRowsPerPage(l.rowsPerPage,true);this.render();}}else{}},_elLastHighlightedTd:null,_aSelections:null,_oAnchorRecord:null,_oAnchorCell:null,_unselectAllTrEls:function(){var i=c.getElementsByClassName(d.CLASS_SELECTED,"tr",this._elTbody);c.removeClass(i,d.CLASS_SELECTED);},_getSelectionTrigger:function(){var l=this.get("selectionMode");var k={};var o,i,j,n,m;if((l=="cellblock")||(l=="cellrange")||(l=="singlecell")){o=this.getLastSelectedCell();if(!o){return null;}else{i=this.getRecord(o.recordId);j=this.getRecordIndex(i);n=this.getTrEl(i);m=this.getTrIndex(n);if(m===null){return null;}else{k.record=i;k.recordIndex=j;k.el=this.getTdEl(o);k.trIndex=m;k.column=this.getColumn(o.columnKey);k.colKeyIndex=k.column.getKeyIndex();k.cell=o;return k;}}}else{i=this.getLastSelectedRecord();if(!i){return null;}else{i=this.getRecord(i);j=this.getRecordIndex(i);n=this.getTrEl(i);m=this.getTrIndex(n);if(m===null){return null;}else{k.record=i;k.recordIndex=j;k.el=n;k.trIndex=m;return k;}}}},_getSelectionAnchor:function(k){var j=this.get("selectionMode");var l={};var m,o,i;if((j=="cellblock")||(j=="cellrange")||(j=="singlecell")){var n=this._oAnchorCell;if(!n){if(k){n=this._oAnchorCell=k.cell;}else{return null;}}m=this._oAnchorCell.record;o=this._oRecordSet.getRecordIndex(m);i=this.getTrIndex(m);if(i===null){if(o<this.getRecordIndex(this.getFirstTrEl())){i=0;}else{i=this.getRecordIndex(this.getLastTrEl());
+}}l.record=m;l.recordIndex=o;l.trIndex=i;l.column=this._oAnchorCell.column;l.colKeyIndex=l.column.getKeyIndex();l.cell=n;return l;}else{m=this._oAnchorRecord;if(!m){if(k){m=this._oAnchorRecord=k.record;}else{return null;}}o=this.getRecordIndex(m);i=this.getTrIndex(m);if(i===null){if(o<this.getRecordIndex(this.getFirstTrEl())){i=0;}else{i=this.getRecordIndex(this.getLastTrEl());}}l.record=m;l.recordIndex=o;l.trIndex=i;return l;}},_handleStandardSelectionByMouse:function(k){var j=k.target;var m=this.getTrEl(j);if(m){var p=k.event;var s=p.shiftKey;var o=p.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&p.metaKey);var r=this.getRecord(m);var l=this._oRecordSet.getRecordIndex(r);var q=this._getSelectionAnchor();var n;if(s&&o){if(q){if(this.isSelected(q.record)){if(q.recordIndex<l){for(n=q.recordIndex+1;n<=l;n++){if(!this.isSelected(n)){this.selectRow(n);}}}else{for(n=q.recordIndex-1;n>=l;n--){if(!this.isSelected(n)){this.selectRow(n);}}}}else{if(q.recordIndex<l){for(n=q.recordIndex+1;n<=l-1;n++){if(this.isSelected(n)){this.unselectRow(n);}}}else{for(n=l+1;n<=q.recordIndex-1;n++){if(this.isSelected(n)){this.unselectRow(n);}}}this.selectRow(r);}}else{this._oAnchorRecord=r;if(this.isSelected(r)){this.unselectRow(r);}else{this.selectRow(r);}}}else{if(s){this.unselectAllRows();if(q){if(q.recordIndex<l){for(n=q.recordIndex;n<=l;n++){this.selectRow(n);}}else{for(n=q.recordIndex;n>=l;n--){this.selectRow(n);}}}else{this._oAnchorRecord=r;this.selectRow(r);}}else{if(o){this._oAnchorRecord=r;if(this.isSelected(r)){this.unselectRow(r);}else{this.selectRow(r);}}else{this._handleSingleSelectionByMouse(k);return;}}}}},_handleStandardSelectionByKey:function(m){var i=g.getCharCode(m);if((i==38)||(i==40)){var k=m.shiftKey;var j=this._getSelectionTrigger();if(!j){return null;}g.stopEvent(m);var l=this._getSelectionAnchor(j);if(k){if((i==40)&&(l.recordIndex<=j.trIndex)){this.selectRow(this.getNextTrEl(j.el));}else{if((i==38)&&(l.recordIndex>=j.trIndex)){this.selectRow(this.getPreviousTrEl(j.el));}else{this.unselectRow(j.el);}}}else{this._handleSingleSelectionByKey(m);}}},_handleSingleSelectionByMouse:function(k){var l=k.target;var j=this.getTrEl(l);if(j){var i=this.getRecord(j);this._oAnchorRecord=i;this.unselectAllRows();this.selectRow(i);}},_handleSingleSelectionByKey:function(l){var i=g.getCharCode(l);if((i==38)||(i==40)){var j=this._getSelectionTrigger();if(!j){return null;}g.stopEvent(l);var k;if(i==38){k=this.getPreviousTrEl(j.el);if(k===null){k=this.getFirstTrEl();}}else{if(i==40){k=this.getNextTrEl(j.el);if(k===null){k=this.getLastTrEl();}}}this.unselectAllRows();this.selectRow(k);this._oAnchorRecord=this.getRecord(k);}},_handleCellBlockSelectionByMouse:function(A){var B=A.target;var l=this.getTdEl(B);if(l){var z=A.event;var q=z.shiftKey;var m=z.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&z.metaKey);var s=this.getTrEl(l);var r=this.getTrIndex(s);var v=this.getColumn(l);var w=v.getKeyIndex();var u=this.getRecord(s);var D=this._oRecordSet.getRecordIndex(u);var p={record:u,column:v};var t=this._getSelectionAnchor();var o=this.getTbodyEl().rows;var n,k,C,y,x;if(q&&m){if(t){if(this.isSelected(t.cell)){if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex+1;y<=w;y++){this.selectCell(s.cells[y]);}}else{if(w<t.colKeyIndex){for(y=w;y<t.colKeyIndex;y++){this.selectCell(s.cells[y]);}}}}else{if(t.recordIndex<D){n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=t.trIndex;y<=r;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x]);}}}else{n=Math.min(t.trIndex,w);k=Math.max(t.trIndex,w);for(y=t.trIndex;y>=r;y--){for(x=k;x>=n;x--){this.selectCell(o[y].cells[x]);}}}}}else{if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex+1;y<w;y++){this.unselectCell(s.cells[y]);}}else{if(w<t.colKeyIndex){for(y=w+1;y<t.colKeyIndex;y++){this.unselectCell(s.cells[y]);}}}}if(t.recordIndex<D){for(y=t.trIndex;y<=r;y++){C=o[y];for(x=0;x<C.cells.length;x++){if(C.sectionRowIndex===t.trIndex){if(x>t.colKeyIndex){this.unselectCell(C.cells[x]);}}else{if(C.sectionRowIndex===r){if(x<w){this.unselectCell(C.cells[x]);}}else{this.unselectCell(C.cells[x]);}}}}}else{for(y=r;y<=t.trIndex;y++){C=o[y];for(x=0;x<C.cells.length;x++){if(C.sectionRowIndex==r){if(x>w){this.unselectCell(C.cells[x]);}}else{if(C.sectionRowIndex==t.trIndex){if(x<t.colKeyIndex){this.unselectCell(C.cells[x]);}}else{this.unselectCell(C.cells[x]);}}}}}this.selectCell(l);}}else{this._oAnchorCell=p;if(this.isSelected(p)){this.unselectCell(p);}else{this.selectCell(p);}}}else{if(q){this.unselectAllCells();if(t){if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex;y<=w;y++){this.selectCell(s.cells[y]);}}else{if(w<t.colKeyIndex){for(y=w;y<=t.colKeyIndex;y++){this.selectCell(s.cells[y]);}}}}else{if(t.recordIndex<D){n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=t.trIndex;y<=r;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x]);}}}else{n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=r;y<=t.trIndex;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x]);}}}}}else{this._oAnchorCell=p;this.selectCell(p);}}else{if(m){this._oAnchorCell=p;if(this.isSelected(p)){this.unselectCell(p);}else{this.selectCell(p);}}else{this._handleSingleCellSelectionByMouse(A);}}}}},_handleCellBlockSelectionByKey:function(o){var j=g.getCharCode(o);var t=o.shiftKey;if((j==9)||!t){this._handleSingleCellSelectionByKey(o);return;}if((j>36)&&(j<41)){var u=this._getSelectionTrigger();if(!u){return null;}g.stopEvent(o);var r=this._getSelectionAnchor(u);var k,s,l,q,m;var p=this.getTbodyEl().rows;var n=u.el.parentNode;if(j==40){if(r.recordIndex<=u.recordIndex){m=this.getNextTrEl(u.el);if(m){s=r.colKeyIndex;l=u.colKeyIndex;if(s>l){for(k=s;k>=l;k--){q=m.cells[k];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=m.cells[k];this.selectCell(q);}}}}else{s=Math.min(r.colKeyIndex,u.colKeyIndex);l=Math.max(r.colKeyIndex,u.colKeyIndex);for(k=s;k<=l;k++){this.unselectCell(n.cells[k]);}}}else{if(j==38){if(r.recordIndex>=u.recordIndex){m=this.getPreviousTrEl(u.el);
+if(m){s=r.colKeyIndex;l=u.colKeyIndex;if(s>l){for(k=s;k>=l;k--){q=m.cells[k];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=m.cells[k];this.selectCell(q);}}}}else{s=Math.min(r.colKeyIndex,u.colKeyIndex);l=Math.max(r.colKeyIndex,u.colKeyIndex);for(k=s;k<=l;k++){this.unselectCell(n.cells[k]);}}}else{if(j==39){if(r.colKeyIndex<=u.colKeyIndex){if(u.colKeyIndex<n.cells.length-1){s=r.trIndex;l=u.trIndex;if(s>l){for(k=s;k>=l;k--){q=p[k].cells[u.colKeyIndex+1];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=p[k].cells[u.colKeyIndex+1];this.selectCell(q);}}}}else{s=Math.min(r.trIndex,u.trIndex);l=Math.max(r.trIndex,u.trIndex);for(k=s;k<=l;k++){this.unselectCell(p[k].cells[u.colKeyIndex]);}}}else{if(j==37){if(r.colKeyIndex>=u.colKeyIndex){if(u.colKeyIndex>0){s=r.trIndex;l=u.trIndex;if(s>l){for(k=s;k>=l;k--){q=p[k].cells[u.colKeyIndex-1];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=p[k].cells[u.colKeyIndex-1];this.selectCell(q);}}}}else{s=Math.min(r.trIndex,u.trIndex);l=Math.max(r.trIndex,u.trIndex);for(k=s;k<=l;k++){this.unselectCell(p[k].cells[u.colKeyIndex]);}}}}}}}},_handleCellRangeSelectionByMouse:function(y){var z=y.target;var k=this.getTdEl(z);if(k){var x=y.event;var o=x.shiftKey;var l=x.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&x.metaKey);var q=this.getTrEl(k);var p=this.getTrIndex(q);var t=this.getColumn(k);var u=t.getKeyIndex();var s=this.getRecord(q);var B=this._oRecordSet.getRecordIndex(s);var n={record:s,column:t};var r=this._getSelectionAnchor();var m=this.getTbodyEl().rows;var A,w,v;if(o&&l){if(r){if(this.isSelected(r.cell)){if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex+1;w<=u;w++){this.selectCell(q.cells[w]);}}else{if(u<r.colKeyIndex){for(w=u;w<r.colKeyIndex;w++){this.selectCell(q.cells[w]);}}}}else{if(r.recordIndex<B){for(w=r.colKeyIndex+1;w<q.cells.length;w++){this.selectCell(q.cells[w]);}for(w=r.trIndex+1;w<p;w++){for(v=0;v<m[w].cells.length;v++){this.selectCell(m[w].cells[v]);}}for(w=0;w<=u;w++){this.selectCell(q.cells[w]);}}else{for(w=u;w<q.cells.length;w++){this.selectCell(q.cells[w]);}for(w=p+1;w<r.trIndex;w++){for(v=0;v<m[w].cells.length;v++){this.selectCell(m[w].cells[v]);}}for(w=0;w<r.colKeyIndex;w++){this.selectCell(q.cells[w]);}}}}else{if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex+1;w<u;w++){this.unselectCell(q.cells[w]);}}else{if(u<r.colKeyIndex){for(w=u+1;w<r.colKeyIndex;w++){this.unselectCell(q.cells[w]);}}}}if(r.recordIndex<B){for(w=r.trIndex;w<=p;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex===r.trIndex){if(v>r.colKeyIndex){this.unselectCell(A.cells[v]);}}else{if(A.sectionRowIndex===p){if(v<u){this.unselectCell(A.cells[v]);}}else{this.unselectCell(A.cells[v]);}}}}}else{for(w=p;w<=r.trIndex;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==p){if(v>u){this.unselectCell(A.cells[v]);}}else{if(A.sectionRowIndex==r.trIndex){if(v<r.colKeyIndex){this.unselectCell(A.cells[v]);}}else{this.unselectCell(A.cells[v]);}}}}}this.selectCell(k);}}else{this._oAnchorCell=n;if(this.isSelected(n)){this.unselectCell(n);}else{this.selectCell(n);}}}else{if(o){this.unselectAllCells();if(r){if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex;w<=u;w++){this.selectCell(q.cells[w]);}}else{if(u<r.colKeyIndex){for(w=u;w<=r.colKeyIndex;w++){this.selectCell(q.cells[w]);}}}}else{if(r.recordIndex<B){for(w=r.trIndex;w<=p;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==r.trIndex){if(v>=r.colKeyIndex){this.selectCell(A.cells[v]);}}else{if(A.sectionRowIndex==p){if(v<=u){this.selectCell(A.cells[v]);}}else{this.selectCell(A.cells[v]);}}}}}else{for(w=p;w<=r.trIndex;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==p){if(v>=u){this.selectCell(A.cells[v]);}}else{if(A.sectionRowIndex==r.trIndex){if(v<=r.colKeyIndex){this.selectCell(A.cells[v]);}}else{this.selectCell(A.cells[v]);}}}}}}}else{this._oAnchorCell=n;this.selectCell(n);}}else{if(l){this._oAnchorCell=n;if(this.isSelected(n)){this.unselectCell(n);}else{this.selectCell(n);}}else{this._handleSingleCellSelectionByMouse(y);}}}}},_handleCellRangeSelectionByKey:function(n){var j=g.getCharCode(n);var r=n.shiftKey;if((j==9)||!r){this._handleSingleCellSelectionByKey(n);return;}if((j>36)&&(j<41)){var s=this._getSelectionTrigger();if(!s){return null;}g.stopEvent(n);var q=this._getSelectionAnchor(s);var k,l,p;var o=this.getTbodyEl().rows;var m=s.el.parentNode;if(j==40){l=this.getNextTrEl(s.el);if(q.recordIndex<=s.recordIndex){for(k=s.colKeyIndex+1;k<m.cells.length;k++){p=m.cells[k];this.selectCell(p);}if(l){for(k=0;k<=s.colKeyIndex;k++){p=l.cells[k];this.selectCell(p);}}}else{for(k=s.colKeyIndex;k<m.cells.length;k++){this.unselectCell(m.cells[k]);}if(l){for(k=0;k<s.colKeyIndex;k++){this.unselectCell(l.cells[k]);}}}}else{if(j==38){l=this.getPreviousTrEl(s.el);if(q.recordIndex>=s.recordIndex){for(k=s.colKeyIndex-1;k>-1;k--){p=m.cells[k];this.selectCell(p);}if(l){for(k=m.cells.length-1;k>=s.colKeyIndex;k--){p=l.cells[k];this.selectCell(p);}}}else{for(k=s.colKeyIndex;k>-1;k--){this.unselectCell(m.cells[k]);}if(l){for(k=m.cells.length-1;k>s.colKeyIndex;k--){this.unselectCell(l.cells[k]);}}}}else{if(j==39){l=this.getNextTrEl(s.el);if(q.recordIndex<s.recordIndex){if(s.colKeyIndex<m.cells.length-1){p=m.cells[s.colKeyIndex+1];this.selectCell(p);}else{if(l){p=l.cells[0];this.selectCell(p);}}}else{if(q.recordIndex>s.recordIndex){this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex<m.cells.length-1){}else{}}else{if(q.colKeyIndex<=s.colKeyIndex){if(s.colKeyIndex<m.cells.length-1){p=m.cells[s.colKeyIndex+1];this.selectCell(p);}else{if(s.trIndex<o.length-1){p=l.cells[0];this.selectCell(p);}}}else{this.unselectCell(m.cells[s.colKeyIndex]);}}}}else{if(j==37){l=this.getPreviousTrEl(s.el);if(q.recordIndex<s.recordIndex){this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex>0){}else{}}else{if(q.recordIndex>s.recordIndex){if(s.colKeyIndex>0){p=m.cells[s.colKeyIndex-1];this.selectCell(p);}else{if(s.trIndex>0){p=l.cells[l.cells.length-1];this.selectCell(p);
+}}}else{if(q.colKeyIndex>=s.colKeyIndex){if(s.colKeyIndex>0){p=m.cells[s.colKeyIndex-1];this.selectCell(p);}else{if(s.trIndex>0){p=l.cells[l.cells.length-1];this.selectCell(p);}}}else{this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex>0){}else{}}}}}}}}}},_handleSingleCellSelectionByMouse:function(n){var o=n.target;var k=this.getTdEl(o);if(k){var j=this.getTrEl(k);var i=this.getRecord(j);var m=this.getColumn(k);var l={record:i,column:m};this._oAnchorCell=l;this.unselectAllCells();this.selectCell(l);}},_handleSingleCellSelectionByKey:function(m){var i=g.getCharCode(m);if((i==9)||((i>36)&&(i<41))){var k=m.shiftKey;var j=this._getSelectionTrigger();if(!j){return null;}var l;if(i==40){l=this.getBelowTdEl(j.el);if(l===null){l=j.el;}}else{if(i==38){l=this.getAboveTdEl(j.el);if(l===null){l=j.el;}}else{if((i==39)||(!k&&(i==9))){l=this.getNextTdEl(j.el);if(l===null){return;}}else{if((i==37)||(k&&(i==9))){l=this.getPreviousTdEl(j.el);if(l===null){return;}}}}}g.stopEvent(m);this.unselectAllCells();this.selectCell(l);this._oAnchorCell={record:this.getRecord(l),column:this.getColumn(l)};}},getSelectedTrEls:function(){return c.getElementsByClassName(d.CLASS_SELECTED,"tr",this._elTbody);},selectRow:function(p){var o,i;if(p instanceof YAHOO.widget.Record){o=this._oRecordSet.getRecord(p);i=this.getTrEl(o);}else{if(h.isNumber(p)){o=this.getRecord(p);i=this.getTrEl(o);}else{i=this.getTrEl(p);o=this.getRecord(i);}}if(o){var n=this._aSelections||[];var m=o.getId();var l=-1;if(n.indexOf){l=n.indexOf(m);}else{for(var k=n.length-1;k>-1;k--){if(n[k]===m){l=k;break;}}}if(l>-1){n.splice(l,1);}n.push(m);this._aSelections=n;if(!this._oAnchorRecord){this._oAnchorRecord=o;}if(i){c.addClass(i,d.CLASS_SELECTED);}this.fireEvent("rowSelectEvent",{record:o,el:i});}else{}},unselectRow:function(p){var i=this.getTrEl(p);var o;if(p instanceof YAHOO.widget.Record){o=this._oRecordSet.getRecord(p);}else{if(h.isNumber(p)){o=this.getRecord(p);}else{o=this.getRecord(i);}}if(o){var n=this._aSelections||[];var m=o.getId();var l=-1;if(n.indexOf){l=n.indexOf(m);}else{for(var k=n.length-1;k>-1;k--){if(n[k]===m){l=k;break;}}}if(l>-1){n.splice(l,1);this._aSelections=n;c.removeClass(i,d.CLASS_SELECTED);this.fireEvent("rowUnselectEvent",{record:o,el:i});return;}}},unselectAllRows:function(){var k=this._aSelections||[],m,l=[];for(var i=k.length-1;i>-1;i--){if(h.isString(k[i])){m=k.splice(i,1);l[l.length]=this.getRecord(h.isArray(m)?m[0]:m);}}this._aSelections=k;this._unselectAllTrEls();this.fireEvent("unselectAllRowsEvent",{records:l});},_unselectAllTdEls:function(){var i=c.getElementsByClassName(d.CLASS_SELECTED,"td",this._elTbody);c.removeClass(i,d.CLASS_SELECTED);},getSelectedTdEls:function(){return c.getElementsByClassName(d.CLASS_SELECTED,"td",this._elTbody);},selectCell:function(i){var p=this.getTdEl(i);if(p){var o=this.getRecord(p);var q=this.getColumn(this.getCellIndex(p));var m=q.getKey();if(o&&m){var n=this._aSelections||[];var l=o.getId();for(var k=n.length-1;k>-1;k--){if((n[k].recordId===l)&&(n[k].columnKey===m)){n.splice(k,1);break;}}n.push({recordId:l,columnKey:m});this._aSelections=n;if(!this._oAnchorCell){this._oAnchorCell={record:o,column:q};}c.addClass(p,d.CLASS_SELECTED);this.fireEvent("cellSelectEvent",{record:o,column:q,key:m,el:p});return;}}},unselectCell:function(i){var o=this.getTdEl(i);if(o){var n=this.getRecord(o);var p=this.getColumn(this.getCellIndex(o));var l=p.getKey();if(n&&l){var m=this._aSelections||[];var q=n.getId();for(var k=m.length-1;k>-1;k--){if((m[k].recordId===q)&&(m[k].columnKey===l)){m.splice(k,1);this._aSelections=m;c.removeClass(o,d.CLASS_SELECTED);this.fireEvent("cellUnselectEvent",{record:n,column:p,key:l,el:o});return;}}}}},unselectAllCells:function(){var k=this._aSelections||[];for(var i=k.length-1;i>-1;i--){if(h.isObject(k[i])){k.splice(i,1);}}this._aSelections=k;this._unselectAllTdEls();this.fireEvent("unselectAllCellsEvent");},isSelected:function(p){if(p&&(p.ownerDocument==document)){return(c.hasClass(this.getTdEl(p),d.CLASS_SELECTED)||c.hasClass(this.getTrEl(p),d.CLASS_SELECTED));}else{var n,k,i;var m=this._aSelections;if(m&&m.length>0){if(p instanceof YAHOO.widget.Record){n=p;}else{if(h.isNumber(p)){n=this.getRecord(p);}}if(n){k=n.getId();if(m.indexOf){if(m.indexOf(k)>-1){return true;}}else{for(i=m.length-1;i>-1;i--){if(m[i]===k){return true;}}}}else{if(p.record&&p.column){k=p.record.getId();var l=p.column.getKey();for(i=m.length-1;i>-1;i--){if((m[i].recordId===k)&&(m[i].columnKey===l)){return true;}}}}}}return false;},getSelectedRows:function(){var i=[];var l=this._aSelections||[];for(var k=0;k<l.length;k++){if(h.isString(l[k])){i.push(l[k]);}}return i;},getSelectedCells:function(){var k=[];var l=this._aSelections||[];for(var i=0;i<l.length;i++){if(l[i]&&h.isObject(l[i])){k.push(l[i]);}}return k;},getLastSelectedRecord:function(){var k=this._aSelections;if(k&&k.length>0){for(var j=k.length-1;j>-1;j--){if(h.isString(k[j])){return k[j];}}}},getLastSelectedCell:function(){var k=this._aSelections;if(k&&k.length>0){for(var j=k.length-1;j>-1;j--){if(k[j].recordId&&k[j].columnKey){return k[j];}}}},highlightRow:function(k){var i=this.getTrEl(k);if(i){var j=this.getRecord(i);c.addClass(i,d.CLASS_HIGHLIGHTED);this.fireEvent("rowHighlightEvent",{record:j,el:i});return;}},unhighlightRow:function(k){var i=this.getTrEl(k);if(i){var j=this.getRecord(i);c.removeClass(i,d.CLASS_HIGHLIGHTED);this.fireEvent("rowUnhighlightEvent",{record:j,el:i});return;}},highlightCell:function(i){var l=this.getTdEl(i);if(l){if(this._elLastHighlightedTd){this.unhighlightCell(this._elLastHighlightedTd);}var k=this.getRecord(l);var m=this.getColumn(this.getCellIndex(l));var j=m.getKey();c.addClass(l,d.CLASS_HIGHLIGHTED);this._elLastHighlightedTd=l;this.fireEvent("cellHighlightEvent",{record:k,column:m,key:j,el:l});return;}},unhighlightCell:function(i){var k=this.getTdEl(i);if(k){var j=this.getRecord(k);c.removeClass(k,d.CLASS_HIGHLIGHTED);this._elLastHighlightedTd=null;this.fireEvent("cellUnhighlightEvent",{record:j,column:this.getColumn(this.getCellIndex(k)),key:this.getColumn(this.getCellIndex(k)).getKey(),el:k});
+return;}},addCellEditor:function(j,i){j.editor=i;j.editor.subscribe("showEvent",this._onEditorShowEvent,this,true);j.editor.subscribe("keydownEvent",this._onEditorKeydownEvent,this,true);j.editor.subscribe("revertEvent",this._onEditorRevertEvent,this,true);j.editor.subscribe("saveEvent",this._onEditorSaveEvent,this,true);j.editor.subscribe("cancelEvent",this._onEditorCancelEvent,this,true);j.editor.subscribe("blurEvent",this._onEditorBlurEvent,this,true);j.editor.subscribe("blockEvent",this._onEditorBlockEvent,this,true);j.editor.subscribe("unblockEvent",this._onEditorUnblockEvent,this,true);},getCellEditor:function(){return this._oCellEditor;},showCellEditor:function(p,q,l){p=this.getTdEl(p);if(p){l=this.getColumn(p);if(l&&l.editor){var j=this._oCellEditor;if(j){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{if(j.isActive){this.cancelCellEditor();}}}if(l.editor instanceof YAHOO.widget.BaseCellEditor){j=l.editor;var n=j.attach(this,p);if(n){j.render();j.move();n=this.doBeforeShowCellEditor(j);if(n){j.show();this._oCellEditor=j;}}}else{if(!q||!(q instanceof YAHOO.widget.Record)){q=this.getRecord(p);}if(!l||!(l instanceof YAHOO.widget.Column)){l=this.getColumn(p);}if(q&&l){if(!this._oCellEditor||this._oCellEditor.container){this._initCellEditorEl();}j=this._oCellEditor;j.cell=p;j.record=q;j.column=l;j.validator=(l.editorOptions&&h.isFunction(l.editorOptions.validator))?l.editorOptions.validator:null;j.value=q.getData(l.key);j.defaultValue=null;var k=j.container;var o=c.getX(p);var m=c.getY(p);if(isNaN(o)||isNaN(m)){o=p.offsetLeft+c.getX(this._elTbody.parentNode)-this._elTbody.scrollLeft;m=p.offsetTop+c.getY(this._elTbody.parentNode)-this._elTbody.scrollTop+this._elThead.offsetHeight;}k.style.left=o+"px";k.style.top=m+"px";this.doBeforeShowCellEditor(this._oCellEditor);k.style.display="";g.addListener(k,"keydown",function(s,r){if((s.keyCode==27)){r.cancelCellEditor();r.focusTbodyEl();}else{r.fireEvent("editorKeydownEvent",{editor:r._oCellEditor,event:s});}},this);var i;if(h.isString(l.editor)){switch(l.editor){case"checkbox":i=d.editCheckbox;break;case"date":i=d.editDate;break;case"dropdown":i=d.editDropdown;break;case"radio":i=d.editRadio;break;case"textarea":i=d.editTextarea;break;case"textbox":i=d.editTextbox;break;default:i=null;}}else{if(h.isFunction(l.editor)){i=l.editor;}}if(i){i(this._oCellEditor,this);if(!l.editorOptions||!l.editorOptions.disableBtns){this.showCellEditorBtns(k);}j.isActive=true;this.fireEvent("editorShowEvent",{editor:j});return;}}}}}},_initCellEditorEl:function(){var i=document.createElement("div");i.id=this._sId+"-celleditor";i.style.display="none";i.tabIndex=0;c.addClass(i,d.CLASS_EDITOR);var k=c.getFirstChild(document.body);if(k){i=c.insertBefore(i,k);}else{i=document.body.appendChild(i);}var j={};j.container=i;j.value=null;j.isActive=false;this._oCellEditor=j;},doBeforeShowCellEditor:function(i){return true;},saveCellEditor:function(){if(this._oCellEditor){if(this._oCellEditor.save){this._oCellEditor.save();}else{if(this._oCellEditor.isActive){var i=this._oCellEditor.value;var j=this._oCellEditor.record.getData(this._oCellEditor.column.key);if(this._oCellEditor.validator){i=this._oCellEditor.value=this._oCellEditor.validator.call(this,i,j,this._oCellEditor);if(i===null){this.resetCellEditor();this.fireEvent("editorRevertEvent",{editor:this._oCellEditor,oldData:j,newData:i});return;}}this._oRecordSet.updateRecordValue(this._oCellEditor.record,this._oCellEditor.column.key,this._oCellEditor.value);this.formatCell(this._oCellEditor.cell.firstChild,this._oCellEditor.record,this._oCellEditor.column);this._oChainRender.add({method:function(){this.validateColumnWidths();},scope:this});this._oChainRender.run();this.resetCellEditor();this.fireEvent("editorSaveEvent",{editor:this._oCellEditor,oldData:j,newData:i});}}}},cancelCellEditor:function(){if(this._oCellEditor){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{if(this._oCellEditor.isActive){this.resetCellEditor();this.fireEvent("editorCancelEvent",{editor:this._oCellEditor});}}}},destroyCellEditor:function(){if(this._oCellEditor){this._oCellEditor.destroy();this._oCellEditor=null;}},_onEditorShowEvent:function(i){this.fireEvent("editorShowEvent",i);},_onEditorKeydownEvent:function(i){this.fireEvent("editorKeydownEvent",i);},_onEditorRevertEvent:function(i){this.fireEvent("editorRevertEvent",i);},_onEditorSaveEvent:function(i){this.fireEvent("editorSaveEvent",i);},_onEditorCancelEvent:function(i){this.fireEvent("editorCancelEvent",i);},_onEditorBlurEvent:function(i){this.fireEvent("editorBlurEvent",i);},_onEditorBlockEvent:function(i){this.fireEvent("editorBlockEvent",i);},_onEditorUnblockEvent:function(i){this.fireEvent("editorUnblockEvent",i);},onEditorBlurEvent:function(i){if(i.editor.disableBtns){if(i.editor.save){i.editor.save();}}else{if(i.editor.cancel){i.editor.cancel();}}},onEditorBlockEvent:function(i){this.disable();},onEditorUnblockEvent:function(i){this.undisable();},doBeforeLoadData:function(i,j,k){return true;},onEventSortColumn:function(k){var i=k.event;var m=k.target;var j=this.getThEl(m)||this.getTdEl(m);if(j){var l=this.getColumn(j);if(l.sortable){g.stopEvent(i);this.sortColumn(l);}}else{}},onEventSelectColumn:function(i){this.selectColumn(i.target);},onEventHighlightColumn:function(i){this.highlightColumn(i.target);},onEventUnhighlightColumn:function(i){this.unhighlightColumn(i.target);},onEventSelectRow:function(j){var i=this.get("selectionMode");if(i=="single"){this._handleSingleSelectionByMouse(j);}else{this._handleStandardSelectionByMouse(j);}},onEventSelectCell:function(j){var i=this.get("selectionMode");if(i=="cellblock"){this._handleCellBlockSelectionByMouse(j);}else{if(i=="cellrange"){this._handleCellRangeSelectionByMouse(j);}else{this._handleSingleCellSelectionByMouse(j);}}},onEventHighlightRow:function(i){this.highlightRow(i.target);},onEventUnhighlightRow:function(i){this.unhighlightRow(i.target);},onEventHighlightCell:function(i){this.highlightCell(i.target);
+},onEventUnhighlightCell:function(i){this.unhighlightCell(i.target);},onEventFormatCell:function(i){var l=i.target;var j=this.getTdEl(l);if(j){var k=this.getColumn(this.getCellIndex(j));this.formatCell(j.firstChild,this.getRecord(j),k);}else{}},onEventShowCellEditor:function(i){if(!this.isDisabled()){this.showCellEditor(i.target);}},onEventSaveCellEditor:function(i){if(this._oCellEditor){if(this._oCellEditor.save){this._oCellEditor.save();}else{this.saveCellEditor();}}},onEventCancelCellEditor:function(i){if(this._oCellEditor){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{this.cancelCellEditor();}}},onDataReturnInitializeTable:function(i,j,k){if((this instanceof d)&&this._sId){this.initializeTable();this.onDataReturnSetRows(i,j,k);}},onDataReturnReplaceRows:function(m,l,n){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:m,response:l,payload:n});var j=this.doBeforeLoadData(m,l,n),k=this.get("paginator"),i=0;if(j&&l&&!l.error&&h.isArray(l.results)){this._oRecordSet.reset();if(this.get("dynamicData")){if(n&&n.pagination&&h.isNumber(n.pagination.recordOffset)){i=n.pagination.recordOffset;}else{if(k){i=k.getStartIndex();}}}this._oRecordSet.setRecords(l.results,i|0);this._handleDataReturnPayload(m,l,n);this.render();}else{if(j&&l.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnAppendRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.addRows(k.results);this._handleDataReturnPayload(j,k,l);}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnInsertRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.addRows(k.results,(l?l.insertIndex:0));this._handleDataReturnPayload(j,k,l);}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnUpdateRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.updateRows((l?l.updateIndex:0),k.results);this._handleDataReturnPayload(j,k,l);}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnSetRows:function(m,l,n){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:m,response:l,payload:n});var j=this.doBeforeLoadData(m,l,n),k=this.get("paginator"),i=0;if(j&&l&&!l.error&&h.isArray(l.results)){if(this.get("dynamicData")){if(n&&n.pagination&&h.isNumber(n.pagination.recordOffset)){i=n.pagination.recordOffset;}else{if(k){i=k.getStartIndex();}}this._oRecordSet.reset();}this._oRecordSet.setRecords(l.results,i|0);this._handleDataReturnPayload(m,l,n);this.render();}else{if(j&&l.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}else{}},handleDataReturnPayload:function(j,i,k){return k||{};},_handleDataReturnPayload:function(k,j,l){l=this.handleDataReturnPayload(k,j,l);if(l){var i=this.get("paginator");if(i){if(this.get("dynamicData")){if(e.Paginator.isNumeric(l.totalRecords)){i.set("totalRecords",l.totalRecords);}}else{i.set("totalRecords",this._oRecordSet.getLength());}if(h.isObject(l.pagination)){i.set("rowsPerPage",l.pagination.rowsPerPage);i.set("recordOffset",l.pagination.recordOffset);}}if(l.sortedBy){this.set("sortedBy",l.sortedBy);}else{if(l.sorting){this.set("sortedBy",l.sorting);}}}},showCellEditorBtns:function(k){var l=k.appendChild(document.createElement("div"));c.addClass(l,d.CLASS_BUTTON);var j=l.appendChild(document.createElement("button"));c.addClass(j,d.CLASS_DEFAULT);j.innerHTML="OK";g.addListener(j,"click",function(n,m){m.onEventSaveCellEditor(n,m);m.focusTbodyEl();},this,true);var i=l.appendChild(document.createElement("button"));i.innerHTML="Cancel";g.addListener(i,"click",function(n,m){m.onEventCancelCellEditor(n,m);m.focusTbodyEl();},this,true);},resetCellEditor:function(){var i=this._oCellEditor.container;i.style.display="none";g.purgeElement(i,true);i.innerHTML="";this._oCellEditor.value=null;this._oCellEditor.isActive=false;},getBody:function(){return this.getTbodyEl();},getCell:function(i){return this.getTdEl(i);},getRow:function(i){return this.getTrEl(i);},refreshView:function(){this.render();},select:function(k){if(!h.isArray(k)){k=[k];}for(var j=0;j<k.length;j++){this.selectRow(k[j]);}},onEventEditCell:function(i){this.onEventShowCellEditor(i);},_syncColWidths:function(){this.validateColumnWidths();}});d.prototype.onDataReturnSetRecords=d.prototype.onDataReturnSetRows;d.prototype.onPaginatorChange=d.prototype.onPaginatorChangeRequest;d.editCheckbox=function(){};d.editDate=function(){};d.editDropdown=function(){};d.editRadio=function(){};d.editTextarea=function(){};d.editTextbox=function(){};})();(function(){var c=YAHOO.lang,f=YAHOO.util,e=YAHOO.widget,a=YAHOO.env.ua,d=f.Dom,j=f.Event,i=f.DataSourceBase,g=e.DataTable,b=e.Paginator;e.ScrollingDataTable=function(n,m,k,l){l=l||{};if(l.scrollable){l.scrollable=false;}this._init();e.ScrollingDataTable.superclass.constructor.call(this,n,m,k,l);this.subscribe("columnShowEvent",this._onColumnChange);};var h=e.ScrollingDataTable;c.augmentObject(h,{CLASS_HEADER:"yui-dt-hd",CLASS_BODY:"yui-dt-bd"});c.extend(h,g,{_elHdContainer:null,_elHdTable:null,_elBdContainer:null,_elBdThead:null,_elTmpContainer:null,_elTmpTable:null,_bScrollbarX:null,initAttributes:function(k){k=k||{};h.superclass.initAttributes.call(this,k);this.setAttributeConfig("width",{value:null,validator:c.isString,method:function(l){if(this._elHdContainer&&this._elBdContainer){this._elHdContainer.style.width=l;this._elBdContainer.style.width=l;this._syncScrollX();this._syncScrollOverhang();}}});this.setAttributeConfig("height",{value:null,validator:c.isString,method:function(l){if(this._elHdContainer&&this._elBdContainer){this._elBdContainer.style.height=l;
+this._syncScrollX();this._syncScrollY();this._syncScrollOverhang();}}});this.setAttributeConfig("COLOR_COLUMNFILLER",{value:"#F2F2F2",validator:c.isString,method:function(l){if(this._elHdContainer){this._elHdContainer.style.backgroundColor=l;}}});},_init:function(){this._elHdContainer=null;this._elHdTable=null;this._elBdContainer=null;this._elBdThead=null;this._elTmpContainer=null;this._elTmpTable=null;},_initDomElements:function(k){this._initContainerEl(k);if(this._elContainer&&this._elHdContainer&&this._elBdContainer){this._initTableEl();if(this._elHdTable&&this._elTable){this._initColgroupEl(this._elHdTable);this._initTheadEl(this._elHdTable,this._elTable);this._initTbodyEl(this._elTable);this._initMsgTbodyEl(this._elTable);}}if(!this._elContainer||!this._elTable||!this._elColgroup||!this._elThead||!this._elTbody||!this._elMsgTbody||!this._elHdTable||!this._elBdThead){return false;}else{return true;}},_destroyContainerEl:function(k){d.removeClass(k,g.CLASS_SCROLLABLE);h.superclass._destroyContainerEl.call(this,k);this._elHdContainer=null;this._elBdContainer=null;},_initContainerEl:function(l){h.superclass._initContainerEl.call(this,l);if(this._elContainer){l=this._elContainer;d.addClass(l,g.CLASS_SCROLLABLE);var k=document.createElement("div");k.style.width=this.get("width")||"";k.style.backgroundColor=this.get("COLOR_COLUMNFILLER");d.addClass(k,h.CLASS_HEADER);this._elHdContainer=k;l.appendChild(k);var m=document.createElement("div");m.style.width=this.get("width")||"";m.style.height=this.get("height")||"";d.addClass(m,h.CLASS_BODY);j.addListener(m,"scroll",this._onScroll,this);this._elBdContainer=m;l.appendChild(m);}},_initCaptionEl:function(k){},_destroyHdTableEl:function(){var k=this._elHdTable;if(k){j.purgeElement(k,true);k.parentNode.removeChild(k);this._elBdThead=null;}},_initTableEl:function(){if(this._elHdContainer){this._destroyHdTableEl();this._elHdTable=this._elHdContainer.appendChild(document.createElement("table"));j.delegate(this._elHdTable,"mouseenter",this._onTableMouseover,"thead ."+g.CLASS_LABEL,this);j.delegate(this._elHdTable,"mouseleave",this._onTableMouseout,"thead ."+g.CLASS_LABEL,this);}h.superclass._initTableEl.call(this,this._elBdContainer);},_initTheadEl:function(l,k){l=l||this._elHdTable;k=k||this._elTable;this._initBdTheadEl(k);h.superclass._initTheadEl.call(this,l);},_initThEl:function(l,k){h.superclass._initThEl.call(this,l,k);l.id=this.getId()+"-fixedth-"+k.getSanitizedKey();},_destroyBdTheadEl:function(){var k=this._elBdThead;if(k){var l=k.parentNode;j.purgeElement(k,true);l.removeChild(k);this._elBdThead=null;this._destroyColumnHelpers();}},_initBdTheadEl:function(t){if(t){this._destroyBdTheadEl();var p=t.insertBefore(document.createElement("thead"),t.firstChild);var v=this._oColumnSet,u=v.tree,o,l,s,q,n,m,r;for(q=0,m=u.length;q<m;q++){l=p.appendChild(document.createElement("tr"));for(n=0,r=u[q].length;n<r;n++){s=u[q][n];o=l.appendChild(document.createElement("th"));this._initBdThEl(o,s,q,n);}}this._elBdThead=p;}},_initBdThEl:function(n,m){n.id=this.getId()+"-th-"+m.getSanitizedKey();n.rowSpan=m.getRowspan();n.colSpan=m.getColspan();if(m.abbr){n.abbr=m.abbr;}var l=m.getKey();var k=c.isValue(m.label)?m.label:l;n.innerHTML=k;},_initTbodyEl:function(k){h.superclass._initTbodyEl.call(this,k);k.style.marginTop=(this._elTbody.offsetTop>0)?"-"+this._elTbody.offsetTop+"px":0;},_focusEl:function(l){l=l||this._elTbody;var k=this;this._storeScrollPositions();setTimeout(function(){setTimeout(function(){try{l.focus();k._restoreScrollPositions();}catch(m){}},0);},0);},_runRenderChain:function(){this._storeScrollPositions();this._oChainRender.run();},_storeScrollPositions:function(){this._nScrollTop=this._elBdContainer.scrollTop;this._nScrollLeft=this._elBdContainer.scrollLeft;},clearScrollPositions:function(){this._nScrollTop=0;this._nScrollLeft=0;},_restoreScrollPositions:function(){if(this._nScrollTop){this._elBdContainer.scrollTop=this._nScrollTop;this._nScrollTop=null;}if(this._nScrollLeft){this._elBdContainer.scrollLeft=this._nScrollLeft;this._elHdContainer.scrollLeft=this._nScrollLeft;this._nScrollLeft=null;}},_validateColumnWidth:function(n,k){if(!n.width&&!n.hidden){var p=n.getThEl();if(n._calculatedWidth){this._setColumnWidth(n,"auto","visible");}if(p.offsetWidth!==k.offsetWidth){var m=(p.offsetWidth>k.offsetWidth)?n.getThLinerEl():k.firstChild;var l=Math.max(0,(m.offsetWidth-(parseInt(d.getStyle(m,"paddingLeft"),10)|0)-(parseInt(d.getStyle(m,"paddingRight"),10)|0)),n.minWidth);var o="visible";if((n.maxAutoWidth>0)&&(l>n.maxAutoWidth)){l=n.maxAutoWidth;o="hidden";}this._elTbody.style.display="none";this._setColumnWidth(n,l+"px",o);n._calculatedWidth=l;this._elTbody.style.display="";}}},validateColumnWidths:function(s){var u=this._oColumnSet.keys,w=u.length,l=this.getFirstTrEl();if(a.ie){this._setOverhangValue(1);}if(u&&l&&(l.childNodes.length===w)){var m=this.get("width");if(m){this._elHdContainer.style.width="";this._elBdContainer.style.width="";}this._elContainer.style.width="";if(s&&c.isNumber(s.getKeyIndex())){this._validateColumnWidth(s,l.childNodes[s.getKeyIndex()]);}else{var t,k=[],o,q,r;for(q=0;q<w;q++){s=u[q];if(!s.width&&!s.hidden&&s._calculatedWidth){k[k.length]=s;}}this._elTbody.style.display="none";for(q=0,r=k.length;q<r;q++){this._setColumnWidth(k[q],"auto","visible");}this._elTbody.style.display="";k=[];for(q=0;q<w;q++){s=u[q];t=l.childNodes[q];if(!s.width&&!s.hidden){var n=s.getThEl();if(n.offsetWidth!==t.offsetWidth){var v=(n.offsetWidth>t.offsetWidth)?s.getThLinerEl():t.firstChild;var p=Math.max(0,(v.offsetWidth-(parseInt(d.getStyle(v,"paddingLeft"),10)|0)-(parseInt(d.getStyle(v,"paddingRight"),10)|0)),s.minWidth);var x="visible";if((s.maxAutoWidth>0)&&(p>s.maxAutoWidth)){p=s.maxAutoWidth;x="hidden";}k[k.length]=[s,p,x];}}}this._elTbody.style.display="none";for(q=0,r=k.length;q<r;q++){o=k[q];this._setColumnWidth(o[0],o[1]+"px",o[2]);o[0]._calculatedWidth=o[1];}this._elTbody.style.display="";}if(m){this._elHdContainer.style.width=m;this._elBdContainer.style.width=m;
+}}this._syncScroll();this._restoreScrollPositions();},_syncScroll:function(){this._syncScrollX();this._syncScrollY();this._syncScrollOverhang();if(a.opera){this._elHdContainer.scrollLeft=this._elBdContainer.scrollLeft;if(!this.get("width")){document.body.style+="";}}},_syncScrollY:function(){var k=this._elTbody,l=this._elBdContainer;if(!this.get("width")){this._elContainer.style.width=(l.scrollHeight>l.clientHeight)?(k.parentNode.clientWidth+19)+"px":(k.parentNode.clientWidth+2)+"px";}},_syncScrollX:function(){var k=this._elTbody,l=this._elBdContainer;if(!this.get("height")&&(a.ie)){l.style.height=(l.scrollWidth>l.offsetWidth)?(k.parentNode.offsetHeight+18)+"px":k.parentNode.offsetHeight+"px";}if(this._elTbody.rows.length===0){this._elMsgTbody.parentNode.style.width=this.getTheadEl().parentNode.offsetWidth+"px";}else{this._elMsgTbody.parentNode.style.width="";}},_syncScrollOverhang:function(){var l=this._elBdContainer,k=1;if((l.scrollHeight>l.clientHeight)&&(l.scrollWidth>l.clientWidth)){k=18;}this._setOverhangValue(k);},_setOverhangValue:function(n){var p=this._oColumnSet.headers[this._oColumnSet.headers.length-1]||[],l=p.length,k=this._sId+"-fixedth-",o=n+"px solid "+this.get("COLOR_COLUMNFILLER");this._elThead.style.display="none";for(var m=0;m<l;m++){d.get(k+p[m]).style.borderRight=o;}this._elThead.style.display="";},getHdContainerEl:function(){return this._elHdContainer;},getBdContainerEl:function(){return this._elBdContainer;},getHdTableEl:function(){return this._elHdTable;},getBdTableEl:function(){return this._elTable;},disable:function(){var k=this._elMask;k.style.width=this._elBdContainer.offsetWidth+"px";k.style.height=this._elHdContainer.offsetHeight+this._elBdContainer.offsetHeight+"px";k.style.display="";this.fireEvent("disableEvent");},removeColumn:function(m){var k=this._elHdContainer.scrollLeft;var l=this._elBdContainer.scrollLeft;m=h.superclass.removeColumn.call(this,m);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=l;return m;},insertColumn:function(n,l){var k=this._elHdContainer.scrollLeft;var m=this._elBdContainer.scrollLeft;var o=h.superclass.insertColumn.call(this,n,l);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=m;return o;},reorderColumn:function(n,l){var k=this._elHdContainer.scrollLeft;var m=this._elBdContainer.scrollLeft;var o=h.superclass.reorderColumn.call(this,n,l);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=m;return o;},setColumnWidth:function(l,k){l=this.getColumn(l);if(l){this._storeScrollPositions();if(c.isNumber(k)){k=(k>l.minWidth)?k:l.minWidth;l.width=k;this._setColumnWidth(l,k+"px");this._syncScroll();this.fireEvent("columnSetWidthEvent",{column:l,width:k});}else{if(k===null){l.width=k;this._setColumnWidth(l,"auto");this.validateColumnWidths(l);this.fireEvent("columnUnsetWidthEvent",{column:l});}}this._clearTrTemplateEl();}else{}},scrollTo:function(m){var l=this.getTdEl(m);if(l){this.clearScrollPositions();this.getBdContainerEl().scrollLeft=l.offsetLeft;this.getBdContainerEl().scrollTop=l.parentNode.offsetTop;}else{var k=this.getTrEl(m);if(k){this.clearScrollPositions();this.getBdContainerEl().scrollTop=k.offsetTop;}}},showTableMessage:function(o,k){var p=this._elMsgTd;if(c.isString(o)){p.firstChild.innerHTML=o;}if(c.isString(k)){d.addClass(p.firstChild,k);}var n=this.getTheadEl();var l=n.parentNode;var m=l.offsetWidth;this._elMsgTbody.parentNode.style.width=this.getTheadEl().parentNode.offsetWidth+"px";this._elMsgTbody.style.display="";this.fireEvent("tableMsgShowEvent",{html:o,className:k});},_onColumnChange:function(k){var l=(k.column)?k.column:(k.editor)?k.editor.column:null;this._storeScrollPositions();this.validateColumnWidths(l);},_onScroll:function(m,l){l._elHdContainer.scrollLeft=l._elBdContainer.scrollLeft;if(l._oCellEditor&&l._oCellEditor.isActive){l.fireEvent("editorBlurEvent",{editor:l._oCellEditor});l.cancelCellEditor();}var n=j.getTarget(m);var k=n.nodeName.toLowerCase();l.fireEvent("tableScrollEvent",{event:m,target:n});},_onTheadKeydown:function(n,l){if(j.getCharCode(n)===9){setTimeout(function(){if((l instanceof h)&&l._sId){l._elBdContainer.scrollLeft=l._elHdContainer.scrollLeft;}},0);}var o=j.getTarget(n);var k=o.nodeName.toLowerCase();var m=true;while(o&&(k!="table")){switch(k){case"body":return;case"input":case"textarea":break;case"thead":m=l.fireEvent("theadKeyEvent",{target:o,event:n});break;default:break;}if(m===false){return;}else{o=o.parentNode;if(o){k=o.nodeName.toLowerCase();}}}l.fireEvent("tableKeyEvent",{target:(o||l._elContainer),event:n});}});})();(function(){var c=YAHOO.lang,f=YAHOO.util,e=YAHOO.widget,b=YAHOO.env.ua,d=f.Dom,i=f.Event,h=e.DataTable;e.BaseCellEditor=function(k,j){this._sId=this._sId||d.generateId(null,"yui-ceditor");YAHOO.widget.BaseCellEditor._nCount++;this._sType=k;this._initConfigs(j);this._initEvents();this._needsRender=true;};var a=e.BaseCellEditor;c.augmentObject(a,{_nCount:0,CLASS_CELLEDITOR:"yui-ceditor"});a.prototype={_sId:null,_sType:null,_oDataTable:null,_oColumn:null,_oRecord:null,_elTd:null,_elContainer:null,_elCancelBtn:null,_elSaveBtn:null,_initConfigs:function(k){if(k&&YAHOO.lang.isObject(k)){for(var j in k){if(j){this[j]=k[j];}}}},_initEvents:function(){this.createEvent("showEvent");this.createEvent("keydownEvent");this.createEvent("invalidDataEvent");this.createEvent("revertEvent");this.createEvent("saveEvent");this.createEvent("cancelEvent");this.createEvent("blurEvent");this.createEvent("blockEvent");this.createEvent("unblockEvent");},_initContainerEl:function(){if(this._elContainer){YAHOO.util.Event.purgeElement(this._elContainer,true);this._elContainer.innerHTML="";}var j=document.createElement("div");j.id=this.getId()+"-container";j.style.display="none";j.tabIndex=0;this.className=c.isArray(this.className)?this.className:this.className?[this.className]:[];this.className[this.className.length]=h.CLASS_EDITOR;j.className=this.className.join(" ");document.body.insertBefore(j,document.body.firstChild);this._elContainer=j;},_initShimEl:function(){if(this.useIFrame){if(!this._elIFrame){var j=document.createElement("iframe");
+j.src="javascript:false";j.frameBorder=0;j.scrolling="no";j.style.display="none";j.className=h.CLASS_EDITOR_SHIM;j.tabIndex=-1;j.role="presentation";j.title="Presentational iframe shim";document.body.insertBefore(j,document.body.firstChild);this._elIFrame=j;}}},_hide:function(){this.getContainerEl().style.display="none";if(this._elIFrame){this._elIFrame.style.display="none";}this.isActive=false;this.getDataTable()._oCellEditor=null;},asyncSubmitter:null,value:null,defaultValue:null,validator:null,resetInvalidData:true,isActive:false,LABEL_SAVE:"Save",LABEL_CANCEL:"Cancel",disableBtns:false,useIFrame:false,className:null,toString:function(){return"CellEditor instance "+this._sId;},getId:function(){return this._sId;},getDataTable:function(){return this._oDataTable;},getColumn:function(){return this._oColumn;},getRecord:function(){return this._oRecord;},getTdEl:function(){return this._elTd;},getContainerEl:function(){return this._elContainer;},destroy:function(){this.unsubscribeAll();var k=this.getColumn();if(k){k.editor=null;}var j=this.getContainerEl();if(j){i.purgeElement(j,true);j.parentNode.removeChild(j);}},render:function(){if(!this._needsRender){return;}this._initContainerEl();this._initShimEl();i.addListener(this.getContainerEl(),"keydown",function(l,j){if((l.keyCode==27)){var k=i.getTarget(l);if(k.nodeName&&k.nodeName.toLowerCase()==="select"){k.blur();}j.cancel();}j.fireEvent("keydownEvent",{editor:j,event:l});},this);this.renderForm();if(!this.disableBtns){this.renderBtns();}this.doAfterRender();this._needsRender=false;},renderBtns:function(){var l=this.getContainerEl().appendChild(document.createElement("div"));l.className=h.CLASS_BUTTON;var k=l.appendChild(document.createElement("button"));k.className=h.CLASS_DEFAULT;k.innerHTML=this.LABEL_SAVE;i.addListener(k,"click",function(m){this.save();},this,true);this._elSaveBtn=k;var j=l.appendChild(document.createElement("button"));j.innerHTML=this.LABEL_CANCEL;i.addListener(j,"click",function(m){this.cancel();},this,true);this._elCancelBtn=j;},attach:function(n,l){if(n instanceof YAHOO.widget.DataTable){this._oDataTable=n;l=n.getTdEl(l);if(l){this._elTd=l;var m=n.getColumn(l);if(m){this._oColumn=m;var j=n.getRecord(l);if(j){this._oRecord=j;var k=j.getData(this.getColumn().getField());this.value=(k!==undefined)?k:this.defaultValue;return true;}}}}return false;},move:function(){var m=this.getContainerEl(),l=this.getTdEl(),j=d.getX(l),n=d.getY(l);if(isNaN(j)||isNaN(n)){var k=this.getDataTable().getTbodyEl();j=l.offsetLeft+d.getX(k.parentNode)-k.scrollLeft;n=l.offsetTop+d.getY(k.parentNode)-k.scrollTop+this.getDataTable().getTheadEl().offsetHeight;}m.style.left=j+"px";m.style.top=n+"px";if(this._elIFrame){this._elIFrame.style.left=j+"px";this._elIFrame.style.top=n+"px";}},show:function(){var k=this.getContainerEl(),j=this._elIFrame;this.resetForm();this.isActive=true;k.style.display="";if(j){j.style.width=k.offsetWidth+"px";j.style.height=k.offsetHeight+"px";j.style.display="";}this.focus();this.fireEvent("showEvent",{editor:this});},block:function(){this.fireEvent("blockEvent",{editor:this});},unblock:function(){this.fireEvent("unblockEvent",{editor:this});},save:function(){var k=this.getInputValue();var l=k;if(this.validator){l=this.validator.call(this.getDataTable(),k,this.value,this);if(l===undefined){if(this.resetInvalidData){this.resetForm();}this.fireEvent("invalidDataEvent",{editor:this,oldData:this.value,newData:k});return;}}var m=this;var j=function(o,n){var p=m.value;if(o){m.value=n;m.getDataTable().updateCell(m.getRecord(),m.getColumn(),n);m._hide();m.fireEvent("saveEvent",{editor:m,oldData:p,newData:m.value});}else{m.resetForm();m.fireEvent("revertEvent",{editor:m,oldData:p,newData:n});}m.unblock();};this.block();if(c.isFunction(this.asyncSubmitter)){this.asyncSubmitter.call(this,j,l);}else{j(true,l);}},cancel:function(){if(this.isActive){this._hide();this.fireEvent("cancelEvent",{editor:this});}else{}},renderForm:function(){},doAfterRender:function(){},handleDisabledBtns:function(){},resetForm:function(){},focus:function(){},getInputValue:function(){}};c.augmentProto(a,f.EventProvider);e.CheckboxCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-checkboxceditor");YAHOO.widget.BaseCellEditor._nCount++;e.CheckboxCellEditor.superclass.constructor.call(this,j.type||"checkbox",j);};c.extend(e.CheckboxCellEditor,a,{checkboxOptions:null,checkboxes:null,value:null,renderForm:function(){if(c.isArray(this.checkboxOptions)){var n,o,q,l,m,k;for(m=0,k=this.checkboxOptions.length;m<k;m++){n=this.checkboxOptions[m];o=c.isValue(n.value)?n.value:n;q=this.getId()+"-chk"+m;this.getContainerEl().innerHTML+='<input type="checkbox"'+' id="'+q+'"'+' value="'+o+'" />';l=this.getContainerEl().appendChild(document.createElement("label"));l.htmlFor=q;l.innerHTML=c.isValue(n.label)?n.label:n;}var p=[];for(m=0;m<k;m++){p[p.length]=this.getContainerEl().childNodes[m*2];}this.checkboxes=p;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){i.addListener(this.getContainerEl(),"click",function(j){if(i.getTarget(j).tagName.toLowerCase()==="input"){this.save();}},this,true);},resetForm:function(){var p=c.isArray(this.value)?this.value:[this.value];for(var o=0,n=this.checkboxes.length;o<n;o++){this.checkboxes[o].checked=false;for(var m=0,l=p.length;m<l;m++){if(this.checkboxes[o].value==p[m]){this.checkboxes[o].checked=true;}}}},focus:function(){this.checkboxes[0].focus();},getInputValue:function(){var k=[];for(var m=0,l=this.checkboxes.length;m<l;m++){if(this.checkboxes[m].checked){k[k.length]=this.checkboxes[m].value;}}return k;}});c.augmentObject(e.CheckboxCellEditor,a);e.DateCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-dateceditor");YAHOO.widget.BaseCellEditor._nCount++;e.DateCellEditor.superclass.constructor.call(this,j.type||"date",j);};c.extend(e.DateCellEditor,a,{calendar:null,calendarOptions:null,defaultValue:new Date(),renderForm:function(){if(YAHOO.widget.Calendar){var k=this.getContainerEl().appendChild(document.createElement("div"));
+k.id=this.getId()+"-dateContainer";var l=new YAHOO.widget.Calendar(this.getId()+"-date",k.id,this.calendarOptions);l.render();k.style.cssFloat="none";l.hideEvent.subscribe(function(){this.cancel();},this,true);if(b.ie){var j=this.getContainerEl().appendChild(document.createElement("div"));j.style.clear="both";}this.calendar=l;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){this.calendar.selectEvent.subscribe(function(j){this.save();},this,true);},resetForm:function(){var j=this.value||(new Date());this.calendar.select(j);this.calendar.cfg.setProperty("pagedate",j,false);this.calendar.render();this.calendar.show();},focus:function(){},getInputValue:function(){return this.calendar.getSelectedDates()[0];}});c.augmentObject(e.DateCellEditor,a);e.DropdownCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-dropdownceditor");YAHOO.widget.BaseCellEditor._nCount++;e.DropdownCellEditor.superclass.constructor.call(this,j.type||"dropdown",j);};c.extend(e.DropdownCellEditor,a,{dropdownOptions:null,dropdown:null,multiple:false,size:null,renderForm:function(){var n=this.getContainerEl().appendChild(document.createElement("select"));n.style.zoom=1;if(this.multiple){n.multiple="multiple";}if(c.isNumber(this.size)){n.size=this.size;}this.dropdown=n;if(c.isArray(this.dropdownOptions)){var o,m;for(var l=0,k=this.dropdownOptions.length;l<k;l++){o=this.dropdownOptions[l];m=document.createElement("option");m.value=(c.isValue(o.value))?o.value:o;m.innerHTML=(c.isValue(o.label))?o.label:o;m=n.appendChild(m);}if(this.disableBtns){this.handleDisabledBtns();}}},handleDisabledBtns:function(){if(this.multiple){i.addListener(this.dropdown,"blur",function(j){this.save();},this,true);}else{if(!b.ie){i.addListener(this.dropdown,"change",function(j){this.save();},this,true);}else{i.addListener(this.dropdown,"blur",function(j){this.save();},this,true);i.addListener(this.dropdown,"click",function(j){this.save();},this,true);}}},resetForm:function(){var s=this.dropdown.options,p=0,o=s.length;if(c.isArray(this.value)){var l=this.value,k=0,r=l.length,q={};for(;p<o;p++){s[p].selected=false;q[s[p].value]=s[p];}for(;k<r;k++){if(q[l[k]]){q[l[k]].selected=true;}}}else{for(;p<o;p++){if(this.value==s[p].value){s[p].selected=true;}}}},focus:function(){this.getDataTable()._focusEl(this.dropdown);},getInputValue:function(){var n=this.dropdown.options;if(this.multiple){var k=[],m=0,l=n.length;for(;m<l;m++){if(n[m].selected){k.push(n[m].value);}}return k;}else{return n[n.selectedIndex].value;}}});c.augmentObject(e.DropdownCellEditor,a);e.RadioCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-radioceditor");YAHOO.widget.BaseCellEditor._nCount++;e.RadioCellEditor.superclass.constructor.call(this,j.type||"radio",j);};c.extend(e.RadioCellEditor,a,{radios:null,radioOptions:null,renderForm:function(){if(c.isArray(this.radioOptions)){var k,l,r,o;for(var n=0,p=this.radioOptions.length;n<p;n++){k=this.radioOptions[n];l=c.isValue(k.value)?k.value:k;r=this.getId()+"-radio"+n;this.getContainerEl().innerHTML+='<input type="radio"'+' name="'+this.getId()+'"'+' value="'+l+'"'+' id="'+r+'" />';o=this.getContainerEl().appendChild(document.createElement("label"));o.htmlFor=r;o.innerHTML=(c.isValue(k.label))?k.label:k;}var q=[],s;for(var m=0;m<p;m++){s=this.getContainerEl().childNodes[m*2];q[q.length]=s;}this.radios=q;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){i.addListener(this.getContainerEl(),"click",function(j){if(i.getTarget(j).tagName.toLowerCase()==="input"){this.save();}},this,true);},resetForm:function(){for(var m=0,l=this.radios.length;m<l;m++){var k=this.radios[m];if(this.value==k.value){k.checked=true;return;}}},focus:function(){for(var l=0,k=this.radios.length;l<k;l++){if(this.radios[l].checked){this.radios[l].focus();return;}}},getInputValue:function(){for(var l=0,k=this.radios.length;l<k;l++){if(this.radios[l].checked){return this.radios[l].value;}}}});c.augmentObject(e.RadioCellEditor,a);e.TextareaCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-textareaceditor");YAHOO.widget.BaseCellEditor._nCount++;e.TextareaCellEditor.superclass.constructor.call(this,j.type||"textarea",j);};c.extend(e.TextareaCellEditor,a,{textarea:null,renderForm:function(){var j=this.getContainerEl().appendChild(document.createElement("textarea"));this.textarea=j;if(this.disableBtns){this.handleDisabledBtns();}},handleDisabledBtns:function(){i.addListener(this.textarea,"blur",function(j){this.save();},this,true);},move:function(){this.textarea.style.width=this.getTdEl().offsetWidth+"px";this.textarea.style.height="3em";YAHOO.widget.TextareaCellEditor.superclass.move.call(this);},resetForm:function(){this.textarea.value=this.value;},focus:function(){this.getDataTable()._focusEl(this.textarea);this.textarea.select();},getInputValue:function(){return this.textarea.value;}});c.augmentObject(e.TextareaCellEditor,a);e.TextboxCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-textboxceditor");YAHOO.widget.BaseCellEditor._nCount++;e.TextboxCellEditor.superclass.constructor.call(this,j.type||"textbox",j);};c.extend(e.TextboxCellEditor,a,{textbox:null,renderForm:function(){var j;if(b.webkit>420){j=this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));}else{j=this.getContainerEl().appendChild(document.createElement("input"));}j.type="text";this.textbox=j;i.addListener(j,"keypress",function(k){if((k.keyCode===13)){YAHOO.util.Event.preventDefault(k);this.save();}},this,true);if(this.disableBtns){this.handleDisabledBtns();}},move:function(){this.textbox.style.width=this.getTdEl().offsetWidth+"px";e.TextboxCellEditor.superclass.move.call(this);},resetForm:function(){this.textbox.value=c.isValue(this.value)?this.value.toString():"";},focus:function(){this.getDataTable()._focusEl(this.textbox);this.textbox.select();},getInputValue:function(){return this.textbox.value;
+}});c.augmentObject(e.TextboxCellEditor,a);h.Editors={checkbox:e.CheckboxCellEditor,"date":e.DateCellEditor,dropdown:e.DropdownCellEditor,radio:e.RadioCellEditor,textarea:e.TextareaCellEditor,textbox:e.TextboxCellEditor};e.CellEditor=function(k,j){if(k&&h.Editors[k]){c.augmentObject(a,h.Editors[k]);return new h.Editors[k](j);}else{return new a(null,j);}};var g=e.CellEditor;c.augmentObject(g,a);})();YAHOO.register("datatable",YAHOO.widget.DataTable,{version:"2.9.0",build:"2800"});
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/_data_table/_dt_elements.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,78 @@
+## DATA TABLE RE USABLE ELEMENTS
+## usage:
+## <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
+
+<%def name="quick_menu(repo_name)">
+  <ul class="menu_items hidden">
+    <li style="border-top:1px solid #003367;margin-left:18px;padding-left:-99px"></li>
+    <li>
+       <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
+       <span class="icon">
+           <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
+       </span>
+       <span>${_('Summary')}</span>
+       </a>
+    </li>
+    <li>
+       <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
+       <span class="icon">
+           <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
+       </span>
+       <span>${_('Changelog')}</span>
+       </a>
+    </li>
+    <li>
+       <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
+       <span class="icon">
+           <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
+       </span>
+       <span>${_('Files')}</span>
+       </a>
+    </li>
+    <li>
+       <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
+       <span class="icon">
+           <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Fork')}" />
+       </span>
+       <span>${_('Fork')}</span>
+       </a>
+    </li>
+  </ul>
+</%def>
+
+<%def name="repo_name(name,rtype,private,fork_of)">
+  <div style="white-space: nowrap">
+   ##TYPE OF REPO
+   %if h.is_hg(rtype):
+     <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
+   %elif h.is_git(rtype):
+     <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
+   %endif
+
+   ##PRIVATE/PUBLIC
+   %if private:
+      <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
+   %else:
+      <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
+   %endif
+
+   ##NAME
+   ${h.link_to(name,h.url('summary_home',repo_name=name),class_="repo_name")}
+   %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>
+   %endif
+  </div>
+</%def>
+
+
+
+<%def name="revision(name,rev,tip,author,last_msg)">
+  <div>
+  %if rev >= 0:
+      <pre><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></pre>
+  %else:
+      ${_('No changesets yet')}
+  %endif
+  </div>
+</%def>
--- a/rhodecode/templates/admin/admin.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/admin.html	Sun Feb 26 17:25:09 2012 +0200
@@ -24,5 +24,5 @@
 	        ${c.log_data}
 	    </div>
 	</div>
-</div>    
-</%def>
\ No newline at end of file
+</div>
+</%def>
--- a/rhodecode/templates/admin/admin_log.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/admin_log.html	Sun Feb 26 17:25:09 2012 +0200
@@ -23,7 +23,7 @@
 		  ${l.repository_name}
 		%endif
 		</td>
-		
+
 		<td>${l.action_date}</td>
 		<td>${l.user_ip}</td>
 	</tr>
@@ -36,7 +36,7 @@
 		ypjax(e.target.href,"user_log",function(){show_more_event();tooltip_activate();});
 		YUE.preventDefault(e);
 	},'.pager_link');
-	
+
 	YUE.delegate("user_log","click",function(e,matchedEl,container){
 	      var el = e.target;
 	      YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
@@ -48,6 +48,6 @@
 <div class="pagination-wh pagination-left">
 ${c.users_log.pager('$link_previous ~2~ $link_next')}
 </div>
-%else: 
-	${_('No actions yet')} 
-%endif
\ No newline at end of file
+%else:
+	${_('No actions yet')}
+%endif
--- a/rhodecode/templates/admin/ldap/ldap.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/ldap/ldap.html	Sun Feb 26 17:25:09 2012 +0200
@@ -6,9 +6,9 @@
 </%def>
 
 <%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))} 
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
     &raquo;
-    ${_('Ldap')}    
+    ${_('Ldap')}
 </%def>
 
 <%def name="page_nav()">
@@ -19,7 +19,7 @@
 <div class="box">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}       
+        ${self.breadcrumbs()}
     </div>
     ${h.form(url('ldap_settings'))}
     <div class="form">
@@ -84,20 +84,12 @@
                 <div class="label"><label for="ldap_attr_email">${_('E-mail Attribute')}</label></div>
                 <div class="input">${h.text('ldap_attr_email',class_='small')}</div>
             </div>
-            
+
             <div class="buttons">
             ${h.submit('save',_('Save'),class_="ui-button")}
-            </div>              
+            </div>
         </div>
-    </div>     
-    ${h.end_form()}    
+    </div>
+    ${h.end_form()}
 </div>
-</%def>    
-
-
-
-
-
-
-   
-
+</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/notifications/notifications.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,53 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('My Notifications')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${_('My Notifications')}
+</%def>
+
+<%def name="page_nav()">
+	${self.menu('admin')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+        ##<ul class="links">
+        ##    <li>
+        ##      <span style="text-transform: uppercase;"><a href="#">${_('Compose message')}</a></span>
+        ##    </li>
+        ##</ul>
+    </div>
+    %if c.notifications:
+      <div style="padding:10px 15px;text-align: right">
+      <span id='mark_all_read' class="ui-btn">${_('Mark all read')}</span>
+      </div>
+    %endif
+  <div id='notification_data'>
+    <%include file='notifications_data.html'/>
+  </div>
+</div>
+<script type="text/javascript">
+var url_del = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
+YUE.on(YUQ('.delete-notification'),'click',function(e){
+ var notification_id = e.currentTarget.id;
+ deleteNotification(url_del,notification_id)
+})
+ YUE.on('mark_all_read','click',function(e){
+	    var url = "${h.url('notifications_mark_all_read')}";
+	    ypjax(url,'notification_data',function(){
+	    	YUD.get('notification_counter').innerHTML=0;
+	    	YUE.on(YUQ('.delete-notification'),'click',function(e){
+	    		 var notification_id = e.currentTarget.id;
+	    		 deleteNotification(url_del,notification_id)
+	    	})
+	    });
+ })
+</script>
+</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/notifications/notifications_data.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,28 @@
+
+%if c.notifications:
+<%
+unread = lambda n:{False:'unread'}.get(n)
+%>
+<div class="table">
+  <div class="notification-list">
+  %for notification in c.notifications:
+    <div id="notification_${notification.notification.notification_id}" class="container ${unread(notification.read)}">
+      <div class="notification-header">
+        <div class="gravatar">
+            <img alt="gravatar" src="${h.gravatar_url(h.email(notification.notification.created_by_user.email),24)}"/>
+        </div>
+        <div class="desc ${unread(notification.read)}">
+        <a href="${url('notification', notification_id=notification.notification.notification_id)}">${notification.notification.description}</a>
+        </div>
+        <div class="delete-notifications">
+          <span id="${notification.notification.notification_id}" class="delete-notification delete_icon action"></span>
+        </div>
+      </div>
+      <div class="notification-subject">${h.literal(notification.notification.subject)}</div>
+    </div>
+  %endfor
+  </div>
+</div>
+%else:
+    <div class="table">${_('No notifications here yet')}</div>
+%endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/notifications/show_notification.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,54 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('Show notification')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${h.link_to(_('Notifications'),h.url('notifications'))}
+    &raquo;
+    ${_('Show notification')}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('admin')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+        ##<ul class="links">
+        ##    <li>
+        ##      <span style="text-transform: uppercase;"><a href="#">${_('Compose message')}</a></span>
+        ##    </li>
+        ##</ul>
+    </div>
+    <div class="table">
+      <div id="notification_${c.notification.notification_id}">
+        <div class="notification-header">
+          <div class="gravatar">
+              <img alt="gravatar" src="${h.gravatar_url(h.email(c.notification.created_by_user.email),24)}"/>
+          </div>
+          <div class="desc">
+              ${c.notification.description}
+          </div>
+          <div class="delete-notifications">
+            <span id="${c.notification.notification_id}" class="delete-notification delete_icon action"></span>
+          </div>
+        </div>
+        <div>${h.rst_w_mentions(c.notification.body)}</div>
+      </div>
+    </div>
+</div>
+<script type="text/javascript">
+var url = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
+var main = "${url('notifications')}";
+   YUE.on(YUQ('.delete-notification'),'click',function(e){
+       var notification_id = e.currentTarget.id;
+       deleteNotification(url,notification_id,[function(){window.location=main}])
+   })
+</script>
+</%def>
--- a/rhodecode/templates/admin/permissions/permissions.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/permissions/permissions.html	Sun Feb 26 17:25:09 2012 +0200
@@ -6,9 +6,9 @@
 </%def>
 
 <%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))} 
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
     &raquo;
-    ${_('Permissions')}    
+    ${_('Permissions')}
 </%def>
 
 <%def name="page_nav()">
@@ -19,7 +19,7 @@
 <div class="box">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}       
+        ${self.breadcrumbs()}
     </div>
     <h3>${_('Default permissions')}</h3>
     ${h.form(url('permission', id='default'),method='put')}
@@ -35,21 +35,21 @@
                         ${h.checkbox('anonymous',True)}
                     </div>
                 </div>
-            </div>        
+            </div>
 			<div class="field">
 				<div class="label label-select">
 					<label for="default_perm">${_('Repository permission')}:</label>
 				</div>
 				<div class="select">
 					${h.select('default_perm','',c.perms_choices)}
-				
+
 		                ${h.checkbox('overwrite_default','true')}
 		                <label for="overwrite_default">
-		                <span class="tooltip" 
+		                <span class="tooltip"
 		                title="${h.tooltip(_('All default permissions on each repository will be reset to choosen permission, note that all custom default permission on repositories will be lost'))}">
 		                ${_('overwrite existing settings')}</span> </label>
-				</div>		                
-			</div>   
+				</div>
+			</div>
 			<div class="field">
 		        <div class="label">
 		            <label for="default_register">${_('Registration')}:</label>
@@ -57,7 +57,7 @@
 				<div class="select">
 					${h.select('default_register','',c.register_choices)}
 				</div>
-			</div> 		
+			</div>
              <div class="field">
                 <div class="label">
                     <label for="default_create">${_('Repository creation')}:</label>
@@ -65,20 +65,13 @@
 				<div class="select">
 					${h.select('default_create','',c.create_choices)}
 				</div>
-             </div>	
-				        
+             </div>
+
 	        <div class="buttons">
 	        ${h.submit('set',_('set'),class_="ui-button")}
-	        </div>	                                                               
+	        </div>
         </div>
-    </div>  
+    </div>
     ${h.end_form()}
 </div>
-</%def>    
-
-
-
-
-
-
-   
+</%def>
--- a/rhodecode/templates/admin/repos/repo_add.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/repos/repo_add.html	Sun Feb 26 17:25:09 2012 +0200
@@ -6,9 +6,9 @@
 </%def>
 
 <%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))} 
-    &raquo; 
-    ${h.link_to(_('Repositories'),h.url('repos'))} 
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${h.link_to(_('Repositories'),h.url('repos'))}
     &raquo;
     ${_('add new')}
 </%def>
@@ -21,8 +21,8 @@
 	<div class="box">
 	    <!-- box / title -->
 	    <div class="title">
-	        ${self.breadcrumbs()}      
+	        ${self.breadcrumbs()}
 	    </div>
         <%include file="repo_add_base.html"/>
-    </div>        
-</%def>   
\ No newline at end of file
+    </div>
+</%def>
--- a/rhodecode/templates/admin/repos/repo_add_base.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/repos/repo_add_base.html	Sun Feb 26 17:25:09 2012 +0200
@@ -22,7 +22,7 @@
             <div class="input">
                 ${h.text('clone_uri',class_="small")}
             </div>
-         </div>             
+         </div>
          <div class="field">
              <div class="label">
                  <label for="repo_group">${_('Repository group')}:</label>
@@ -30,7 +30,7 @@
              <div class="input">
                  ${h.select('repo_group','',c.repo_groups,class_="medium")}
              </div>
-         </div>         
+         </div>
         <div class="field">
             <div class="label">
                 <label for="repo_type">${_('Type')}:</label>
@@ -38,7 +38,7 @@
             <div class="input">
                 ${h.select('repo_type','hg',c.backends,class_="small")}
             </div>
-         </div>             
+         </div>
         <div class="field">
             <div class="label label-textarea">
                 <label for="description">${_('Description')}:</label>
@@ -57,7 +57,7 @@
          </div>
         <div class="buttons">
           ${h.submit('add',_('add'),class_="ui-button")}
-        </div>                                                          
+        </div>
     </div>
-</div>    
-${h.end_form()}    
+</div>
+${h.end_form()}
--- a/rhodecode/templates/admin/repos/repo_add_create_repository.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/repos/repo_add_create_repository.html	Sun Feb 26 17:25:09 2012 +0200
@@ -17,8 +17,8 @@
     <div class="box">
         <!-- box / title -->
         <div class="title">
-            ${self.breadcrumbs()}      
+            ${self.breadcrumbs()}
         </div>
         <%include file="repo_add_base.html"/>
-    </div>        
-</%def>
\ No newline at end of file
+    </div>
+</%def>
--- a/rhodecode/templates/admin/repos/repo_edit.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/repos/repo_edit.html	Sun Feb 26 17:25:09 2012 +0200
@@ -6,9 +6,9 @@
 </%def>
 
 <%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))} 
-    &raquo; 
-    ${h.link_to(_('Repositories'),h.url('repos'))} 
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${h.link_to(_('Repositories'),h.url('repos'))}
     &raquo;
     ${_('edit')} &raquo; ${h.link_to(c.repo_info.just_name,h.url('summary_home',repo_name=c.repo_name))}
 </%def>
@@ -21,7 +21,7 @@
 <div class="box box-left">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}      
+        ${self.breadcrumbs()}
     </div>
     ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
     <div class="form">
@@ -42,7 +42,7 @@
 	           <div class="input">
 	               ${h.text('clone_uri',class_="medium")}
 	           </div>
-	        </div>             
+	        </div>
 	        <div class="field">
 	            <div class="label">
 	                <label for="repo_group">${_('Repository group')}:</label>
@@ -50,7 +50,7 @@
 	            <div class="input">
 	                ${h.select('repo_group','',c.repo_groups,class_="medium")}
 	            </div>
-	        </div>         
+	        </div>
             <div class="field">
                 <div class="label">
                     <label for="repo_type">${_('Type')}:</label>
@@ -58,7 +58,7 @@
                 <div class="input">
                     ${h.select('repo_type','hg',c.backends,class_="medium")}
                 </div>
-            </div>             
+            </div>
             <div class="field">
                 <div class="label label-textarea">
                     <label for="description">${_('Description')}:</label>
@@ -67,7 +67,7 @@
                     ${h.textarea('description',cols=23,rows=5)}
                 </div>
             </div>
-            
+
             <div class="field">
                 <div class="label label-checkbox">
                     <label for="private">${_('Private')}:</label>
@@ -83,7 +83,7 @@
                 <div class="checkboxes">
                     ${h.checkbox('enable_statistics',value="True")}
                 </div>
-            </div>     
+            </div>
             <div class="field">
                 <div class="label label-checkbox">
                     <label for="enable_downloads">${_('Enable downloads')}:</label>
@@ -91,7 +91,7 @@
                 <div class="checkboxes">
                     ${h.checkbox('enable_downloads',value="True")}
                 </div>
-            </div>                      
+            </div>
             <div class="field">
                 <div class="label">
                     <label for="user">${_('Owner')}:</label>
@@ -102,8 +102,8 @@
                        <div id="owner_container"></div>
                     </div>
                 </div>
-             </div>                
-             
+             </div>
+
             <div class="field">
                 <div class="label">
                     <label for="input">${_('Permissions')}:</label>
@@ -111,87 +111,114 @@
                 <div class="input">
                     <%include file="repo_edit_perms.html"/>
                 </div>
-             
-            <div class="buttons">
-              ${h.submit('save','Save',class_="ui-button")}
-              ${h.reset('reset','Reset',class_="ui-button")}
-            </div>                                                          
-        </div>
+
+                <div class="buttons">
+                  ${h.submit('save','Save',class_="ui-button")}
+                  ${h.reset('reset','Reset',class_="ui-button")}
+                </div>
+            </div>
     </div>
-    </div>    
+    </div>
     ${h.end_form()}
 </div>
 
 <div class="box box-right">
     <div class="title">
-        <h5>${_('Administration')}</h5>    
+        <h5>${_('Administration')}</h5>
     </div>
-    
+
         <h3>${_('Statistics')}</h3>
         ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
         <div class="form">
            <div class="fields">
-               ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="refresh_icon action_button",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
-               <div class="field" style="border:none">
+               ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
+               <div class="field" style="border:none;color:#888">
                <ul>
                     <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
-                    <li>${_('Percentage of stats gathered')}: ${c.stats_percentage} %</li>
+                    <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
                </ul>
                </div>
-               
            </div>
-        </div>                    
+        </div>
         ${h.end_form()}
-        
+
         %if c.repo_info.clone_uri:
         <h3>${_('Remote')}</h3>
         ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
         <div class="form">
            <div class="fields">
-               ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="pull_icon action_button",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
+               ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
                <div class="field" style="border:none">
                <ul>
                     <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
-               </ul> 
-               </div>              
+               </ul>
+               </div>
            </div>
-        </div>                    
+        </div>
         ${h.end_form()}
         %endif
-        
+
         <h3>${_('Cache')}</h3>
         ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
         <div class="form">
            <div class="fields">
-               ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="refresh_icon action_button",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
+               ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
            </div>
-        </div>                    
+        </div>
         ${h.end_form()}
-        
+
         <h3>${_('Public journal')}</h3>
         ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
         <div class="form">
-            <div class="fields">
                 ${h.hidden('auth_token',str(h.get_token()))}
+                <div class="field">
                 %if c.in_public_journal:
-                    ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="stop_following_icon action_button")}
+                    ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
                 %else:
-		            ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="start_following_icon action_button")}
+		            ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
 		        %endif
-	         </div>        
+                </div>
+               <div class="field" style="border:none;color:#888">
+               <ul>
+                    <li>${_('''All actions made on this repository will be accessible to everyone in public journal''')}
+                    </li>
+               </ul>
+               </div>
         </div>
         ${h.end_form()}
-        
+
         <h3>${_('Delete')}</h3>
         ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
         <div class="form">
            <div class="fields">
-               ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
+               ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
+           </div>
+           <div class="field" style="border:none;color:#888">
+           <ul>
+                <li>${_('''This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems.
+                         If you need fully delete it from filesystem please do it manually''')}
+                </li>
+           </ul>
            </div>
-        </div>                    
+        </div>
         ${h.end_form()}
-    
+
+        <h3>${_('Set as fork')}</h3>
+        ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
+        <div class="form">
+           <div class="fields">
+               ${h.select('id_fork_of','',c.repos_list,class_="medium")}
+               ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
+           </div>
+               <div class="field" style="border:none;color:#888">
+               <ul>
+                    <li>${_('''Manually set this repository as a fork of another''')}</li>
+               </ul>
+               </div>
+        </div>
+        ${h.end_form()}
+
 </div>
 
 
-</%def> 
+</%def>
--- a/rhodecode/templates/admin/repos/repo_edit_perms.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/repos/repo_edit_perms.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,4 +1,4 @@
-<table id="permissions_manage">
+<table id="permissions_manage" class="noborder">
     <tr>
         <td>${_('none')}</td>
         <td>${_('read')}</td>
@@ -7,7 +7,7 @@
         <td>${_('member')}</td>
         <td></td>
     </tr>
-    ## USERS                        
+    ## USERS
     %for r2p in c.repo_info.repo_to_perm:
         %if r2p.user.username =='default' and c.repo_info.private:
             <tr>
@@ -16,7 +16,7 @@
                     ${_('private repository')}
                     </span>
                 </td>
-                <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url("/images/icons/user.png")}"/>${r2p.user.username}</td>
+                <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${r2p.user.username}</td>
             </tr>
         %else:
         <tr id="id${id(r2p.user.username)}">
@@ -32,13 +32,13 @@
                 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
                 ${_('revoke')}
                 </span>
-              %endif                    
+              %endif
             </td>
         </tr>
         %endif
     %endfor
-    
-    ## USERS GROUPS                          
+
+    ## USERS GROUPS
     %for g2p in c.repo_info.users_group_to_perm:
         <tr id="id${id(g2p.users_group.users_group_name)}">
             <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
@@ -92,10 +92,10 @@
     var postData = '_method=delete&user_id=' + user_id;
     var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
 };
-            
+
 function ajaxActionUsersGroup(users_group_id,field_id){
     var sUrl = "${h.url('delete_repo_users_group',repo_name=c.repo_name)}";
-    var callback = { 
+    var callback = {
     	success:function(o){
     	    var tr = YUD.get(String(field_id));
     		tr.parentNode.removeChild(tr);
@@ -104,7 +104,7 @@
             alert("${_('Failed to remove users group')}");
         },
     };
-    var postData = '_method=delete&users_group_id='+users_group_id; 
+    var postData = '_method=delete&users_group_id='+users_group_id;
     var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
 };
 
@@ -267,7 +267,7 @@
 
     membersAC.itemSelectEvent.subscribe(myHandler);
     if(ownerAC.itemSelectEvent){
-    	ownerAC.itemSelectEvent.subscribe(myHandler);	
+    	ownerAC.itemSelectEvent.subscribe(myHandler);
     }
 
     return {
@@ -277,5 +277,5 @@
         ownerAC: ownerAC,
     };
 }();
-            
-</script>      
\ No newline at end of file
+
+</script>
--- a/rhodecode/templates/admin/repos/repos.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/repos/repos.html	Sun Feb 26 17:25:09 2012 +0200
@@ -14,75 +14,111 @@
 </%def>
 <%def name="main()">
 <div class="box">
-    <!-- box / title -->
+
     <div class="title">
         ${self.breadcrumbs()}
         <ul class="links">
           <li>
-            <span>${h.link_to(_(u'ADD NEW REPOSITORY'),h.url('new_repo'))}</span>
-          </li>          
-        </ul>        
+            <span>${h.link_to(_(u'ADD REPOSITORY'),h.url('new_repo'))}</span>
+          </li>
+        </ul>
     </div>
-    <!-- end box / title -->
+
     <div class="table">
-        <table class="table_disp">
-        <tr class="header">
-	        <th class="left">${_('Name')}</th>
-	        <th class="left">${_('Description')}</th>
-	        <th class="left">${_('Last change')}</th>
-	        <th class="left">${_('Tip')}</th>
-	        <th class="left">${_('Contact')}</th>
-            <th class="left">${_('action')}</th>
-        </tr>
-            %for cnt,repo in enumerate(c.repos_list):
-            <tr class="parity${cnt%2}">
-                 <td>
-                 ## TYPE OF REPO
-                 %if repo['dbrepo']['repo_type'] =='hg':
-                   <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
-                 %elif repo['dbrepo']['repo_type'] =='git':
-                   <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
-                 %else:
-                   
-                 %endif
-                 
-                 ## PRIVATE/PUBLIC REPO                  
-                 %if repo['dbrepo']['private']:
-                    <img alt="${_('private')}" src="${h.url('/images/icons/lock.png')}"/>
-                 %else:
-                    <img alt="${_('public')}" src="${h.url('/images/icons/lock_open.png')}"/>
-                 %endif         
-                ${h.link_to(repo['name'],h.url('edit_repo',repo_name=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 title="${repo['description']}">${h.truncate(repo['description'],60)}</td>
-	            <td>${h.age(repo['last_change'])}</td>
-	            <td>
-	            	%if repo['rev']>=0:
-	            	${h.link_to('r%s:%s' % (repo['rev'],h.short_id(repo['tip'])),
-	                h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
-	                class_="tooltip",
-	                title=h.tooltip(repo['last_msg']))}
-	            	%else:
-	            		${_('No changesets yet')}
-	            	%endif    
-	            </td>
-	            <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
-                <td>
-                  ${h.form(url('repo', repo_name=repo['name']),method='delete')}
-                    ${h.submit('remove_%s' % repo['name'],_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
-                  ${h.end_form()}
-                </td>
-            </tr>
-            %endfor
+        <div id='repos_list_wrap' class="yui-skin-sam">
+        <%cnt=0%>
+        <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
+
+        <table id="repos_list">
+         <thead>
+          <tr>
+            <th class="left"></th>
+  	        <th class="left">${_('Name')}</th>
+  	        <th class="left">${_('Description')}</th>
+  	        <th class="left">${_('Last change')}</th>
+  	        <th class="left">${_('Tip')}</th>
+  	        <th class="left">${_('Contact')}</th>
+            <th class="left">${_('Action')}</th>
+          </tr>
+         </thead>
+
+          %for cnt,repo in enumerate(c.repos_list,1):
+          <tr class="parity${cnt%2}">
+              <td class="quick_repo_menu">
+                ${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'))}
+              </td>
+              ##DESCRIPTION
+              <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
+                 ${h.truncate(repo['description'],60)}</span>
+              </td>
+              ##LAST CHANGE
+              <td>
+                <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
+              </td>
+              ##LAST REVISION
+              <td>
+                  ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
+              </td>
+            <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
+              <td>
+                ${h.form(url('repo', repo_name=repo['name']),method='delete')}
+                  ${h.submit('remove_%s' % repo['name'],_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo['name']+"');")}
+                ${h.end_form()}
+              </td>
+          </tr>
+          %endfor
         </table>
+        </div>
     </div>
-</div> 
-		   
-</%def>    
+</div>
+<script>
+
+  // main table sorting
+  var myColumnDefs = [
+      {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
+      {key:"name",label:"${_('Name')}",sortable:true,
+          sortOptions: { sortFunction: nameSort }},
+      {key:"desc",label:"${_('Description')}",sortable:true},
+      {key:"last_change",label:"${_('Last Change')}",sortable:true,
+          sortOptions: { sortFunction: ageSort }},
+      {key:"tip",label:"${_('Tip')}",sortable:true,
+          sortOptions: { sortFunction: revisionSort }},
+      {key:"owner",label:"${_('Owner')}",sortable:true},
+      {key:"action",label:"${_('Action')}",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:"desc"},
+          {key:"last_change"},
+          {key:"tip"},
+          {key:"owner"},
+          {key:"action"},
+      ]
+  };
+
+  var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
+          {
+            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...')}",
+          }
+  );
+  myDataTable.subscribe('postRenderEvent',function(oArgs) {
+      tooltip_activate();
+      quick_repo_menu();
+  });
+</script>
+</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,270 @@
+<table id="permissions_manage" class="noborder">
+    <tr>
+        <td>${_('none')}</td>
+        <td>${_('read')}</td>
+        <td>${_('write')}</td>
+        <td>${_('admin')}</td>
+        <td>${_('member')}</td>
+        <td></td>
+    </tr>
+    ## USERS
+    %for r2p in c.repos_group.repo_group_to_perm:
+        <tr id="id${id(r2p.user.username)}">
+            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
+            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
+            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
+            <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
+            <td style="white-space: nowrap;">
+                <img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${r2p.user.username}
+            </td>
+            <td>
+              %if r2p.user.username !='default':
+                <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
+                ${_('revoke')}
+                </span>
+              %endif
+            </td>
+        </tr>
+    %endfor
+
+    ## USERS GROUPS
+    %for g2p in c.repos_group.users_group_to_perm:
+        <tr id="id${id(g2p.users_group.users_group_name)}">
+            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
+            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
+            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
+            <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
+            <td style="white-space: nowrap;">
+                <img  style="vertical-align:bottom" 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)}')">
+                ${_('revoke')}
+                </span>
+            </td>
+        </tr>
+    %endfor
+    <tr id="add_perm_input">
+        <td>${h.radio('perm_new_member','group.none')}</td>
+        <td>${h.radio('perm_new_member','group.read')}</td>
+        <td>${h.radio('perm_new_member','group.write')}</td>
+        <td>${h.radio('perm_new_member','group.admin')}</td>
+        <td class='ac'>
+            <div class="perm_ac" id="perm_ac">
+                ${h.text('perm_new_member_name',class_='yui-ac-input')}
+                ${h.hidden('perm_new_member_type')}
+                <div id="perm_container"></div>
+            </div>
+        </td>
+        <td></td>
+    </tr>
+    <tr>
+        <td colspan="6">
+            <span id="add_perm" class="add_icon" style="cursor: pointer;">
+            ${_('Add another member')}
+            </span>
+        </td>
+    </tr>
+</table>
+<script type="text/javascript">
+function ajaxActionUser(user_id, field_id) {
+    var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.name)}";
+    var callback = {
+        success: function (o) {
+            var tr = YUD.get(String(field_id));
+            tr.parentNode.removeChild(tr);
+        },
+        failure: function (o) {
+            alert("${_('Failed to remove user')}");
+        },
+    };
+    var postData = '_method=delete&user_id=' + user_id;
+    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
+};
+
+function ajaxActionUsersGroup(users_group_id,field_id){
+    var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.name)}";
+    var callback = {
+        success:function(o){
+            var tr = YUD.get(String(field_id));
+            tr.parentNode.removeChild(tr);
+        },
+        failure:function(o){
+            alert("${_('Failed to remove users group')}");
+        },
+    };
+    var postData = '_method=delete&users_group_id='+users_group_id;
+    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
+};
+
+YUE.onDOMReady(function () {
+    if (!YUD.hasClass('perm_new_member_name', 'error')) {
+        YUD.setStyle('add_perm_input', 'display', 'none');
+    }
+    YAHOO.util.Event.addListener('add_perm', 'click', function () {
+        YUD.setStyle('add_perm_input', 'display', '');
+        YUD.setStyle('add_perm', 'opacity', '0.6');
+        YUD.setStyle('add_perm', 'cursor', 'default');
+    });
+});
+
+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	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/repos_groups/repos_groups.html	Sun Feb 26 17:25:09 2012 +0200
@@ -5,14 +5,12 @@
 </%def>
 
 <%def name="breadcrumbs()">
-    <span class="groups_breadcrumbs">
-    ${_('Groups')} 
+    <span class="groups_breadcrumbs"> ${_('Groups')}
     %if c.group.parent_group:
-        &raquo; ${h.link_to(c.group.parent_group.name,
-        h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
+        &raquo; ${h.link_to(c.group.parent_group.name,h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
     %endif
     &raquo; "${c.group.name}" ${_('with')}
-    </span> 
+    </span>
 </%def>
 
 <%def name="page_nav()">
@@ -20,4 +18,4 @@
 </%def>
 <%def name="main()">
         <%include file="/index_base.html" args="parent=self"/>
-</%def>    
+</%def>
--- a/rhodecode/templates/admin/repos_groups/repos_groups_add.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/repos_groups/repos_groups_add.html	Sun Feb 26 17:25:09 2012 +0200
@@ -5,9 +5,9 @@
     ${_('Add repos group')} - ${c.rhodecode_name}
 </%def>
 <%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))} 
-    &raquo; 
-    ${h.link_to(_('Repos groups'),h.url('repos_groups'))} 
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
     &raquo;
     ${_('add new repos group')}
 </%def>
@@ -20,7 +20,7 @@
 <div class="box">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}       
+        ${self.breadcrumbs()}
     </div>
     <!-- end box / title -->
     ${h.form(url('repos_groups'))}
@@ -35,7 +35,7 @@
                     ${h.text('group_name',class_='medium')}
                 </div>
              </div>
-             
+
             <div class="field">
                 <div class="label label-textarea">
                     <label for="group_description">${_('Description')}:</label>
@@ -44,7 +44,7 @@
                     ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
                 </div>
              </div>
-             
+
              <div class="field">
                  <div class="label">
                      <label for="group_parent_id">${_('Group parent')}:</label>
@@ -52,13 +52,13 @@
                  <div class="input">
                      ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
                  </div>
-             </div>            
-                         
+             </div>
+
             <div class="buttons">
               ${h.submit('save',_('save'),class_="ui-button")}
-            </div>             
+            </div>
         </div>
     </div>
     ${h.end_form()}
-</div>    
-</%def>    
+</div>
+</%def>
--- a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html	Sun Feb 26 17:25:09 2012 +0200
@@ -5,9 +5,9 @@
     ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
 </%def>
 <%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))} 
-    &raquo; 
-    ${h.link_to(_('Repos groups'),h.url('repos_groups'))} 
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
     &raquo;
     ${_('edit repos group')} "${c.repos_group.name}"
 </%def>
@@ -20,7 +20,7 @@
 <div class="box">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}       
+        ${self.breadcrumbs()}
     </div>
     <!-- end box / title -->
     ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
@@ -35,7 +35,7 @@
                     ${h.text('group_name',class_='medium')}
                 </div>
              </div>
-             
+
 	        <div class="field">
 	            <div class="label label-textarea">
 	                <label for="group_description">${_('Description')}:</label>
@@ -44,7 +44,7 @@
 	                ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
 	            </div>
 	         </div>
-             
+
 	         <div class="field">
 	             <div class="label">
 	                 <label for="group_parent_id">${_('Group parent')}:</label>
@@ -52,13 +52,22 @@
 	             <div class="input">
 	                 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
 	             </div>
-	         </div>             
-                         
+	         </div>
+            <div class="field">
+                <div class="label">
+                    <label for="input">${_('Permissions')}:</label>
+                </div>
+                <div class="input">
+                    <%include file="repos_group_edit_perms.html"/>
+                </div>
+
+            </div>
             <div class="buttons">
-              ${h.submit('save',_('save'),class_="ui-button")}
-            </div>             
+              ${h.submit('save',_('Save'),class_="ui-button")}
+              ${h.reset('reset',_('Reset'),class_="ui-button")}
+            </div>
         </div>
     </div>
     ${h.end_form()}
-</div>    
-</%def>    
+</div>
+</%def>
--- a/rhodecode/templates/admin/repos_groups/repos_groups_show.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/repos_groups/repos_groups_show.html	Sun Feb 26 17:25:09 2012 +0200
@@ -20,25 +20,25 @@
         <ul class="links">
           <li>
             <span>${h.link_to(_(u'ADD NEW GROUP'),h.url('new_repos_group'))}</span>
-          </li>          
-        </ul>        
+          </li>
+        </ul>
     </div>
     <!-- end box / title -->
     <div class="table">
            % if c.groups:
             <table class="table_disp">
-            
+
                 <thead>
                     <tr>
                         <th class="left"><a href="#">${_('Group name')}</a></th>
                         <th class="left"><a href="#">${_('Description')}</a></th>
-                        <th class="left"><a href="#">${_('Number of repositories')}</a></th>
+                        <th class="left"><a href="#">${_('Number of toplevel repositories')}</a></th>
                         <th class="left">${_('action')}</th>
                     </tr>
                 </thead>
-                
+
                 ## REPO GROUPS
-                
+
                 % for gr in c.groups:
                   <tr>
                       <td>
@@ -51,18 +51,18 @@
                       <td><b>${gr.repositories.count()}</b></td>
 		               <td>
 		                 ${h.form(url('repos_group', id=gr.group_id),method='delete')}
-		                   ${h.submit('remove_%s' % gr.name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group')+"');")}
+		                   ${h.submit('remove_%s' % gr.name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group: %s') % gr.name+"');")}
 		                 ${h.end_form()}
-		               </td>                      
+		               </td>
                   </tr>
                 % endfor
-                
+
             </table>
             % else:
                 ${_('There are no repositories groups yet')}
             % endif
-         
+
     </div>
-</div> 
-           
-</%def>    
+</div>
+
+</%def>
--- a/rhodecode/templates/admin/settings/hooks.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/settings/hooks.html	Sun Feb 26 17:25:09 2012 +0200
@@ -17,10 +17,10 @@
 <div class="box">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}       
+        ${self.breadcrumbs()}
     </div>
     <!-- end box / title -->
-    
+
     <h3>${_('Built in hooks - read only')}</h3>
     <div class="form">
         <div class="fields">
@@ -36,29 +36,29 @@
           % endfor
 		</div>
     </div>
-    
+
     <h3>${_('Custom hooks')}</h3>
     ${h.form(url('admin_setting', setting_id='hooks'),method='put')}
     <div class="form">
         <div class="fields">
-        
+
           % for hook in c.custom_hooks:
           <div class="field"  id="${'id%s' % hook.ui_id }">
             <div class="label label">
                 <label for="${hook.ui_key}">${hook.ui_key}</label>
             </div>
-            <div class="input" style="margin-left:280px">                
+            <div class="input" style="margin-left:280px">
                 ${h.hidden('hook_ui_key',hook.ui_key)}
                 ${h.hidden('hook_ui_value',hook.ui_value)}
                 ${h.text('hook_ui_value_new',hook.ui_value,size=60)}
-                <span class="delete_icon action_button" 
+                <span class="delete_icon action_button"
                 onclick="ajaxActionHook(${hook.ui_id},'${'id%s' % hook.ui_id }')">
                 ${_('remove')}
                 </span>
             </div>
           </div>
-          % endfor        
-        
+          % endfor
+
           <div class="field">
             <div class="input" style="margin-left:-180px;position: absolute;">
               <div class="input">
@@ -71,7 +71,7 @@
           </div>
           <div class="buttons" style="margin-left:280px">
              ${h.submit('save',_('Save'),class_="ui-button")}
-          </div>          
+          </div>
         </div>
     </div>
     ${h.end_form()}
@@ -92,5 +92,5 @@
     var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
 };
 </script>
-      
-</%def>    
+
+</%def>
--- a/rhodecode/templates/admin/settings/settings.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/settings/settings.html	Sun Feb 26 17:25:09 2012 +0200
@@ -17,15 +17,15 @@
 <div class="box">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}       
+        ${self.breadcrumbs()}
     </div>
     <!-- end box / title -->
-    
+
     <h3>${_('Remap and rescan repositories')}</h3>
     ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
     <div class="form">
         <!-- fields -->
-        
+
         <div class="fields">
 			<div class="field">
 		        <div class="label label-checkbox">
@@ -40,19 +40,19 @@
 		            </div>
 		        </div>
 			</div>
-                            
+
             <div class="buttons">
             ${h.submit('rescan',_('Rescan repositories'),class_="ui-button")}
-            </div>                                                          
+            </div>
         </div>
-    </div>  
+    </div>
     ${h.end_form()}
-    
+
     <h3>${_('Whoosh indexing')}</h3>
     ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
     <div class="form">
         <!-- fields -->
-        
+
         <div class="fields">
             <div class="field">
                 <div class="label label-checkbox">
@@ -65,21 +65,21 @@
                     </div>
                 </div>
             </div>
-                            
+
             <div class="buttons">
             ${h.submit('reindex',_('Reindex'),class_="ui-button")}
-            </div>                                                          
+            </div>
         </div>
-    </div>  
+    </div>
     ${h.end_form()}
-         
-    <h3>${_('Global application settings')}</h3> 
+
+    <h3>${_('Global application settings')}</h3>
     ${h.form(url('admin_setting', setting_id='global'),method='put')}
     <div class="form">
         <!-- fields -->
-        
+
         <div class="fields">
-             
+
              <div class="field">
                 <div class="label">
                     <label for="rhodecode_title">${_('Application name')}:</label>
@@ -88,7 +88,7 @@
                     ${h.text('rhodecode_title',size=30)}
                 </div>
              </div>
-                          
+
             <div class="field">
                 <div class="label">
                     <label for="rhodecode_realm">${_('Realm text')}:</label>
@@ -97,7 +97,7 @@
                     ${h.text('rhodecode_realm',size=30)}
                 </div>
             </div>
-            
+
             <div class="field">
                 <div class="label">
                     <label for="rhodecode_ga_code">${_('GA code')}:</label>
@@ -106,22 +106,22 @@
                     ${h.text('rhodecode_ga_code',size=30)}
                 </div>
             </div>
-                                                 
+
             <div class="buttons">
                 ${h.submit('save',_('Save settings'),class_="ui-button")}
                 ${h.reset('reset',_('Reset'),class_="ui-button")}
-           </div>                                                          
+           </div>
         </div>
-    </div>      
+    </div>
     ${h.end_form()}
 
-    <h3>${_('Mercurial settings')}</h3> 
+    <h3>${_('Mercurial settings')}</h3>
     ${h.form(url('admin_setting', setting_id='mercurial'),method='put')}
     <div class="form">
         <!-- fields -->
-        
+
         <div class="fields">
-             
+
              <div class="field">
                 <div class="label label-checkbox">
                     <label>${_('Web')}:</label>
@@ -132,15 +132,12 @@
 						<label for="web_push_ssl">${_('require ssl for pushing')}</label>
 					</div>
 				</div>
-             </div>						
+             </div>
 
              <div class="field">
                 <div class="label label-checkbox">
                     <label>${_('Hooks')}:</label>
                 </div>
-                <div class="input">
-                    ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'))}
-                </div>                
                 <div class="checkboxes">
 					<div class="checkbox">
 						${h.checkbox('hooks_changegroup_update','True')}
@@ -157,30 +154,32 @@
                     <div class="checkbox">
                         ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
                         <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
-                    </div>                    										
+                    </div>
 				</div>
-             </div>	
-							                          
+                <div class="input" style="margin-top:10px">
+                    ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
+                </div>
+             </div>
             <div class="field">
                 <div class="label">
                     <label for="paths_root_path">${_('Repositories location')}:</label>
                 </div>
                 <div class="input">
-                    ${h.text('paths_root_path',size=30,readonly="readonly")}			                    
-					<span id="path_unlock" class="tooltip" 
+                    ${h.text('paths_root_path',size=30,readonly="readonly")}
+					<span id="path_unlock" class="tooltip"
 						title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
 		                ${_('unlock')}</span>
                 </div>
             </div>
-                                     
+
             <div class="buttons">
                 ${h.submit('save',_('Save settings'),class_="ui-button")}
                 ${h.reset('reset',_('Reset'),class_="ui-button")}
-           </div>                                                          
+           </div>
         </div>
-    </div>      
+    </div>
     ${h.end_form()}
-    
+
     <script type="text/javascript">
         YAHOO.util.Event.onDOMReady(function(){
             YAHOO.util.Event.addListener('path_unlock','click',function(){
@@ -188,5 +187,28 @@
             });
         });
     </script>
+
+    <h3>${_('Test Email')}</h3>
+    ${h.form(url('admin_setting', setting_id='email'),method='put')}
+    <div class="form">
+        <!-- fields -->
+
+        <div class="fields">
+            <div class="field">
+                <div class="label">
+                    <label for="test_email">${_('Email to')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('test_email',size=30)}
+                </div>
+            </div>
+
+            <div class="buttons">
+            ${h.submit('send',_('Send'),class_="ui-button")}
+            </div>
+        </div>
+    </div>
+    ${h.end_form()}
+
 </div>
-</%def>    
+</%def>
--- a/rhodecode/templates/admin/users/user_add.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/users/user_add.html	Sun Feb 26 17:25:09 2012 +0200
@@ -5,9 +5,9 @@
     ${_('Add user')} - ${c.rhodecode_name}
 </%def>
 <%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))} 
-    &raquo; 
-    ${h.link_to(_('Users'),h.url('users'))} 
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${h.link_to(_('Users'),h.url('users'))}
     &raquo;
     ${_('add new user')}
 </%def>
@@ -20,7 +20,7 @@
 <div class="box">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}       
+        ${self.breadcrumbs()}
     </div>
     <!-- end box / title -->
     ${h.form(url('users'))}
@@ -35,7 +35,7 @@
                     ${h.text('username',class_='small')}
                 </div>
              </div>
-            
+
              <div class="field">
                 <div class="label">
                     <label for="password">${_('Password')}:</label>
@@ -44,7 +44,7 @@
                     ${h.password('password',class_='small')}
                 </div>
              </div>
-             
+
              <div class="field">
                 <div class="label">
                     <label for="password_confirmation">${_('Password confirmation')}:</label>
@@ -52,8 +52,8 @@
                 <div class="input">
                     ${h.password('password_confirmation',class_="small",autocomplete="off")}
                 </div>
-             </div>    
-                         
+             </div>
+
              <div class="field">
                 <div class="label">
                     <label for="name">${_('First Name')}:</label>
@@ -62,7 +62,7 @@
                     ${h.text('name',class_='small')}
                 </div>
              </div>
-            
+
              <div class="field">
                 <div class="label">
                     <label for="lastname">${_('Last Name')}:</label>
@@ -71,7 +71,7 @@
                     ${h.text('lastname',class_='small')}
                 </div>
              </div>
-            
+
              <div class="field">
                 <div class="label">
                     <label for="email">${_('Email')}:</label>
@@ -80,21 +80,21 @@
                     ${h.text('email',class_='small')}
                 </div>
              </div>
-            
+
              <div class="field">
                 <div class="label label-checkbox">
                     <label for="active">${_('Active')}:</label>
                 </div>
                 <div class="checkboxes">
-                    ${h.checkbox('active',value=True)}
+                    ${h.checkbox('active',value=True,checked='checked')}
                 </div>
              </div>
-            
+
             <div class="buttons">
               ${h.submit('save',_('save'),class_="ui-button")}
-            </div>             
+            </div>
     	</div>
     </div>
     ${h.end_form()}
-</div>    
-</%def>    
+</div>
+</%def>
--- a/rhodecode/templates/admin/users/user_edit_my_account.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/users/user_edit_my_account.html	Sun Feb 26 17:25:09 2012 +0200
@@ -18,13 +18,13 @@
 <div class="box box-left">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}       
+        ${self.breadcrumbs()}
     </div>
     <!-- end box / title -->
     <div>
     ${h.form(url('admin_settings_my_account_update'),method='put')}
 	    <div class="form">
-	    
+
              <div class="field">
                 <div class="gravatar_box">
                     <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
@@ -34,15 +34,15 @@
                     <br/>${_('Using')} ${c.user.email}
                     %else:
                     <br/>${c.user.email}
-                    %endif 
+                    %endif
                     </p>
                 </div>
-             </div>   	    
+             </div>
 	        <div class="field">
 	            <div class="label">
 	                <label>${_('API key')}</label> ${c.user.api_key}
 	            </div>
-	        </div>	    
+	        </div>
 	        <div class="fields">
 	             <div class="field">
 	                <div class="label">
@@ -52,7 +52,7 @@
 	                    ${h.text('username',class_="medium")}
 	                </div>
 	             </div>
-	            
+
 	             <div class="field">
 	                <div class="label">
 	                    <label for="new_password">${_('New password')}:</label>
@@ -61,7 +61,7 @@
 	                    ${h.password('new_password',class_="medium",autocomplete="off")}
 	                </div>
 	             </div>
-                 
+
                  <div class="field">
                     <div class="label">
                         <label for="password_confirmation">${_('New password confirmation')}:</label>
@@ -70,7 +70,7 @@
                         ${h.password('password_confirmation',class_="medium",autocomplete="off")}
                     </div>
                  </div>
-                 	            
+
 	             <div class="field">
 	                <div class="label">
 	                    <label for="name">${_('First Name')}:</label>
@@ -79,7 +79,7 @@
 	                    ${h.text('name',class_="medium")}
 	                </div>
 	             </div>
-	            
+
 	             <div class="field">
 	                <div class="label">
 	                    <label for="lastname">${_('Last Name')}:</label>
@@ -88,7 +88,7 @@
 	                    ${h.text('lastname',class_="medium")}
 	                </div>
 	             </div>
-	            
+
 	             <div class="field">
 	                <div class="label">
 	                    <label for="email">${_('Email')}:</label>
@@ -97,30 +97,31 @@
 	                    ${h.text('email',class_="medium")}
 	                </div>
 	             </div>
-	            
+
 	            <div class="buttons">
 	              ${h.submit('save',_('Save'),class_="ui-button")}
 	              ${h.reset('reset',_('Reset'),class_="ui-button")}
-	            </div>             
+	            </div>
 	    	</div>
-	    </div>    	
+	    </div>
     ${h.end_form()}
     </div>
-</div>    
+</div>
 
 <div class="box box-right">
     <!-- box / title -->
     <div class="title">
-        <h5>${_('My repositories')} 
-        <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
+        <h5>
+        <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
+        ${_('My repositories')}
         </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>
-           </li>          
-         </ul>           
-         %endif        
+           </li>
+         </ul>
+         %endif
     </div>
     <!-- end box / title -->
     <div class="table">
@@ -129,98 +130,61 @@
             <tr>
             <th class="left">${_('Name')}</th>
             <th class="left">${_('revision')}</th>
-            <th colspan="2" class="left">${_('action')}</th>            
+            <th colspan="2" class="left">${_('action')}</th>
 	    </thead>
 	     <tbody>
 	     %if c.user_repos:
 		     %for repo in c.user_repos:
 		        <tr>
 		            <td>
-                     %if repo['dbrepo']['repo_type'] =='hg':
-                       <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
-                     %elif repo['dbrepo']['repo_type'] =='git':
-                       <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
+                     %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 		            
+
+                     %endif
 		             %if repo['dbrepo']['private']:
-		                <img class="icon" alt="${_('private')}" src="${h.url("/images/icons/lock.png")}"/>
+		                <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")}"/>
+		                <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']}" 
+		            	title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
 		            	src="${h.url('/images/icons/arrow_divide.png')}"/></a>
-		            %endif		            
-		            </td> 
+		            %endif
+		            </td>
 		            <td><span class="tooltip" title="${repo['last_change']}">${("r%s:%s") % (repo['rev'],h.short_id(repo['tip']))}</span></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')}
-	                    ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
-	                  ${h.end_form()}	            
+	                    ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo['name']+"');")}
+	                  ${h.end_form()}
 		            </td>
 		        </tr>
 		     %endfor
 	     %else:
             <div style="padding:5px 0px 10px 0px;">
-	     	${_('No repositories yet')} 
+	     	${_('No repositories yet')}
 	     	%if h.HasPermissionAny('hg.admin','hg.create.repository')():
-	     		${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-button-small")}
+	     		${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-btn")}
 	     	%endif
             </div>
 	     %endif
 	     </tbody>
 	     </table>
     </div>
-    
 </div>
-    <script type="text/javascript">
-     var D = YAHOO.util.Dom;
-     var E = YAHOO.util.Event;
-     var S = YAHOO.util.Selector;
-     
-     var q_filter = D.get('q_filter');
-     var F = YAHOO.namespace('q_filter'); 
-     
-     E.on(q_filter,'click',function(){
-        q_filter.value = '';
-     });
-
-     F.filterTimeout = null;
-     
-     F.updateFilter  = function() { 
-        // Reset timeout 
-        F.filterTimeout = null;
-        
-        var obsolete = [];
-        var nodes = S.query('div.table tr td a.repo_name');
-        var req = q_filter.value.toLowerCase();
-        for (n in nodes){
-            D.setStyle(nodes[n].parentNode.parentNode,'display','')
-        }
-        if (req){
-            for (n in nodes){
-                if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
-                    obsolete.push(nodes[n]); 
-                }
-            }
-            if(obsolete){
-                for (n in obsolete){
-                    D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
-                }
-            }
-        }
-     }
-     
-     E.on(q_filter,'keyup',function(e){
-         clearTimeout(F.filterTimeout); 
-         F.filterTimeout = setTimeout(F.updateFilter,600); 
-     });
-     
-    </script>
-</%def>  
+<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;
+}
+q_filter(target,nodes,func);
+</script>
+</%def>
--- a/rhodecode/templates/admin/users/users.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/users/users.html	Sun Feb 26 17:25:09 2012 +0200
@@ -22,8 +22,8 @@
           <li>
             <span>${h.link_to(_(u'ADD NEW USER'),h.url('new_user'))}</span>
           </li>
-          
-        </ul>        
+
+        </ul>
     </div>
     <!-- end box / title -->
     <div class="table">
@@ -53,7 +53,7 @@
                     <td>
                         ${h.form(url('delete_user', id=user.user_id),method='delete')}
                             ${h.submit('remove_',_('delete'),id="remove_user_%s" % user.user_id,
-                            class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user')+"');")}
+                            class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user: %s') % user.username+"');")}
                         ${h.end_form()}
                     </td>
                 </tr>
--- a/rhodecode/templates/admin/users_groups/users_group_add.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/users_groups/users_group_add.html	Sun Feb 26 17:25:09 2012 +0200
@@ -5,9 +5,9 @@
     ${_('Add users group')} - ${c.rhodecode_name}
 </%def>
 <%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))} 
-    &raquo; 
-    ${h.link_to(_('Users groups'),h.url('users_groups'))} 
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${h.link_to(_('Users groups'),h.url('users_groups'))}
     &raquo;
     ${_('add new users group')}
 </%def>
@@ -20,7 +20,7 @@
 <div class="box">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}       
+        ${self.breadcrumbs()}
     </div>
     <!-- end box / title -->
     ${h.form(url('users_groups'))}
@@ -35,21 +35,21 @@
                     ${h.text('users_group_name',class_='small')}
                 </div>
              </div>
-            
+
              <div class="field">
                 <div class="label label-checkbox">
                     <label for="users_group_active">${_('Active')}:</label>
                 </div>
                 <div class="checkboxes">
-                    ${h.checkbox('users_group_active',value=True)}
+                    ${h.checkbox('users_group_active',value=True, checked='checked')}
                 </div>
              </div>
-            
+
             <div class="buttons">
               ${h.submit('save',_('save'),class_="ui-button")}
-            </div>             
+            </div>
         </div>
     </div>
     ${h.end_form()}
-</div>    
-</%def>    
+</div>
+</%def>
--- a/rhodecode/templates/admin/users_groups/users_group_edit.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/users_groups/users_group_edit.html	Sun Feb 26 17:25:09 2012 +0200
@@ -6,9 +6,9 @@
 </%def>
 
 <%def name="breadcrumbs_links()">
-    ${h.link_to(_('Admin'),h.url('admin_home'))} 
-    &raquo; 
-    ${h.link_to(_('UsersGroups'),h.url('users_groups'))} 
+    ${h.link_to(_('Admin'),h.url('admin_home'))}
+    &raquo;
+    ${h.link_to(_('UsersGroups'),h.url('users_groups'))}
     &raquo;
     ${_('edit')} "${c.users_group.users_group_name}"
 </%def>
@@ -21,7 +21,7 @@
 <div class="box box-left">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}       
+        ${self.breadcrumbs()}
     </div>
     <!-- end box / title -->
     ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')}
@@ -36,7 +36,7 @@
 	                    ${h.text('users_group_name',class_='small')}
 	                </div>
 	             </div>
-	            
+
 	             <div class="field">
 	                <div class="label label-checkbox">
 	                    <label for="users_group_active">${_('Active')}:</label>
@@ -50,7 +50,7 @@
                         <label for="users_group_active">${_('Members')}:</label>
                     </div>
                     <div class="select">
-	                    <table> 
+	                    <table>
 	                            <tr>
 	                                <td>
 	                                    <div>
@@ -59,193 +59,45 @@
 	                                            ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
 	                                           <div  id="remove_all_elements" style="cursor:pointer;text-align:center">
 	                                               ${_('Remove all elements')}
-	                                               <img alt="remove" style="vertical-align:text-bottom" src="${h.url("/images/icons/arrow_right.png")}"/>
+	                                               <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
 	                                           </div>
 	                                        </div>
 	                                        <div style="float:left;width:20px;padding-top:50px">
-	                                            <img alt="add" id="add_element" 
-	                                                style="padding:2px;cursor:pointer" 
-	                                                src="${h.url("/images/icons/arrow_left.png")}"/>
+	                                            <img alt="add" id="add_element"
+	                                                style="padding:2px;cursor:pointer"
+	                                                src="${h.url('/images/icons/arrow_left.png')}"/>
 	                                            <br />
-	                                            <img alt="remove" id="remove_element" 
-	                                                style="padding:2px;cursor:pointer" 
-	                                                src="${h.url("/images/icons/arrow_right.png")}"/>
+	                                            <img alt="remove" id="remove_element"
+	                                                style="padding:2px;cursor:pointer"
+	                                                src="${h.url('/images/icons/arrow_right.png')}"/>
 	                                        </div>
 	                                        <div style="float:left">
 	                                             <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
 	                                             ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
 	                                             <div id="add_all_elements" style="cursor:pointer;text-align:center">
-	                                                   <img alt="add" style="vertical-align:text-bottom" src="${h.url("/images/icons/arrow_left.png")}"/>
+	                                                   <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
 	                                                    ${_('Add all elements')}
-	                                             </div>	                                        
+	                                             </div>
 	                                        </div>
 	                                    </div>
-	                                </td>           
-	                            </tr>       
-	                    </table>                    
-                    </div>	            
+	                                </td>
+	                            </tr>
+	                    </table>
+                    </div>
 
-                </div>			        
+                </div>
                 <div class="buttons">
                   ${h.submit('save',_('save'),class_="ui-button")}
-                </div>             
-            </div>          
-    </div>        
-${h.end_form()}        
+                </div>
+            </div>
+    </div>
+${h.end_form()}
 </div>
-    
-<script type="text/javascript">
-    YAHOO.util.Event.onDOMReady(function(){
-            var D = YAHOO.util.Dom;
-            var E = YAHOO.util.Event;
-            
-            //definition of containers ID's
-            var available_container = 'available_members';
-            var selected_container = 'users_group_members';
-            
-            //form containing containers id
-            var form_id = 'edit_users_group';
-            
-            //temp container for selected storage.
-            var cache = new Array();
-            var av_cache = new Array();
-            var c =  D.get(selected_container);
-            var ac = D.get(available_container);
-            
-            //get only selected options for further fullfilment
-            for(var i = 0;node =c.options[i];i++){
-                if(node.selected){
-                    //push selected to my temp storage left overs :)
-                    cache.push(node);
-                }
-            }
-            
-            //clear 'selected' select
-            //c.options.length = 0;
 
-            //fill it with remembered options
-            //for(var i = 0;node = cache[i];i++){
-            //   c.options[i]=new Option(node.text, node.value, false, false);
-            //}
-            
-           
-            //get all available options to cache
-            for(var i = 0;node =ac.options[i];i++){
-                    //push selected to my temp storage left overs :)
-                    av_cache.push(node);
-            }            
-            
-            //fill available only with those not in choosen
-            ac.options.length=0;
-            tmp_cache = new Array();
-            
-            for(var i = 0;node = av_cache[i];i++){
-            	var add = true;
-	            for(var i2 = 0;node_2 = cache[i2];i2++){
-	                if(node.value == node_2.value){
-	                	add=false;
-	                	break;
-	                }
-	            }
-	            if(add){
-	            	tmp_cache.push(new Option(node.text, node.value, false, false));
-	            }
-            }            
-            
-            for(var i = 0;node = tmp_cache[i];i++){
-                ac.options[i] = node;
-            }
-            
-            function prompts_action_callback(e){
-                
-                var choosen = D.get(selected_container);  
-                var available = D.get(available_container);
-                
-                //get checked and unchecked options from field
-                function get_checked(from_field){
-                    //temp container for storage.
-                    var sel_cache = new Array();
-                    var oth_cache = new Array();
-                    
-                    for(var i = 0;node = from_field.options[i];i++){
-                        if(node.selected){
-                            //push selected fields :)
-                            sel_cache.push(node);
-                        }
-                        else{
-                        	oth_cache.push(node)
-                        }
-                    }                    
-                    
-                    return [sel_cache,oth_cache]
-                }
-                
-                //fill the field with given options
-                function fill_with(field,options){
-                	//clear firtst
-                	field.options.length=0;
-                    for(var i = 0;node = options[i];i++){
-                            field.options[i]=new Option(node.text, node.value, 
-                                    false, false);
-                    }
-                	
-                }
-                //adds to current field
-                function add_to(field,options){
-                    for(var i = 0;node = options[i];i++){
-                            field.appendChild(new Option(node.text, node.value, 
-                                    false, false));
-                    }
-                }
-                
-                // add action
-                if (this.id=='add_element'){
-                    var c = get_checked(available);
-                    add_to(choosen,c[0]);
-                    fill_with(available,c[1]);
-                }
-                // remove action
-                if (this.id=='remove_element'){
-                    var c = get_checked(choosen);
-                    add_to(available,c[0]);
-                    fill_with(choosen,c[1]);             
-                }                   
-                // add all elements
-                if(this.id=='add_all_elements'){
-                    for(var i=0; node = available.options[i];i++){
-                            choosen.appendChild(new Option(node.text, 
-                                    node.value, false, false));
-                    }
-                    available.options.length = 0;
-                }
-                //remove all elements
-                if(this.id=='remove_all_elements'){
-                    for(var i=0; node = choosen.options[i];i++){
-                        available.appendChild(new Option(node.text, 
-                                node.value, false, false));
-                    }
-                    choosen.options.length = 0;                	
-                }
-                
-            }
-            
-    
-            E.addListener(['add_element','remove_element',
-                           'add_all_elements','remove_all_elements'],'click',
-                           prompts_action_callback)
-
-            E.addListener(form_id,'submit',function(){
-                var choosen = D.get(selected_container);
-                for (var i = 0; i < choosen.options.length; i++) {
-                    choosen.options[i].selected = 'selected';
-                }
-            })  
-        });
-</script>    
 <div class="box box-right">
     <!-- box / title -->
     <div class="title">
-        <h5>${_('Permissions')}</h5>       
+        <h5>${_('Permissions')}</h5>
     </div>
     ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')}
     <div class="form">
@@ -262,9 +114,167 @@
             <div class="buttons">
               ${h.submit('save',_('Save'),class_="ui-button")}
               ${h.reset('reset',_('Reset'),class_="ui-button")}
-            </div>             
-        </div>    
+            </div>
+        </div>
+    </div>
+    ${h.end_form()}
+</div>
+
+<div class="box box-right">
+    <!-- box / title -->
+    <div class="title">
+        <h5>${_('Group members')}</h5>
+    </div>
+    <div class="group_members_wrap">
+      <ul class="group_members">
+      %for user in c.group_members_obj:
+        <li>
+          <div class="group_member">
+            <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div>
+            <div>${user.username}</div>
+            <div>${user.full_name}</div>
+          </div>
+        </li>
+      %endfor
+      </ul>
     </div>
-    ${h.end_form()}    
 </div>
-</%def>  
+<script type="text/javascript">
+YAHOO.util.Event.onDOMReady(function(){
+  var D = YAHOO.util.Dom;
+  var E = YAHOO.util.Event;
+
+  //definition of containers ID's
+  var available_container = 'available_members';
+  var selected_container = 'users_group_members';
+
+  //form containing containers id
+  var form_id = 'edit_users_group';
+
+  //temp container for selected storage.
+  var cache = new Array();
+  var av_cache = new Array();
+  var c =  D.get(selected_container);
+  var ac = D.get(available_container);
+
+  //get only selected options for further fullfilment
+  for(var i = 0;node =c.options[i];i++){
+      if(node.selected){
+          //push selected to my temp storage left overs :)
+          cache.push(node);
+      }
+  }
+
+  //get all available options to cache
+  for(var i = 0;node =ac.options[i];i++){
+          //push selected to my temp storage left overs :)
+          av_cache.push(node);
+  }
+
+  //fill available only with those not in choosen
+  ac.options.length=0;
+  tmp_cache = new Array();
+
+  for(var i = 0;node = av_cache[i];i++){
+      var add = true;
+      for(var i2 = 0;node_2 = cache[i2];i2++){
+          if(node.value == node_2.value){
+              add=false;
+              break;
+          }
+      }
+      if(add){
+          tmp_cache.push(new Option(node.text, node.value, false, false));
+      }
+  }
+
+  for(var i = 0;node = tmp_cache[i];i++){
+      ac.options[i] = node;
+  }
+
+  function prompts_action_callback(e){
+
+      var choosen = D.get(selected_container);
+      var available = D.get(available_container);
+
+      //get checked and unchecked options from field
+      function get_checked(from_field){
+          //temp container for storage.
+          var sel_cache = new Array();
+          var oth_cache = new Array();
+
+          for(var i = 0;node = from_field.options[i];i++){
+              if(node.selected){
+                  //push selected fields :)
+                  sel_cache.push(node);
+              }
+              else{
+                  oth_cache.push(node)
+              }
+          }
+
+          return [sel_cache,oth_cache]
+      }
+
+      //fill the field with given options
+      function fill_with(field,options){
+          //clear firtst
+          field.options.length=0;
+          for(var i = 0;node = options[i];i++){
+                  field.options[i]=new Option(node.text, node.value,
+                          false, false);
+          }
+
+      }
+      //adds to current field
+      function add_to(field,options){
+          for(var i = 0;node = options[i];i++){
+                  field.appendChild(new Option(node.text, node.value,
+                          false, false));
+          }
+      }
+
+      // add action
+      if (this.id=='add_element'){
+          var c = get_checked(available);
+          add_to(choosen,c[0]);
+          fill_with(available,c[1]);
+      }
+      // remove action
+      if (this.id=='remove_element'){
+          var c = get_checked(choosen);
+          add_to(available,c[0]);
+          fill_with(choosen,c[1]);
+      }
+      // add all elements
+      if(this.id=='add_all_elements'){
+          for(var i=0; node = available.options[i];i++){
+                  choosen.appendChild(new Option(node.text,
+                          node.value, false, false));
+          }
+          available.options.length = 0;
+      }
+      //remove all elements
+      if(this.id=='remove_all_elements'){
+          for(var i=0; node = choosen.options[i];i++){
+              available.appendChild(new Option(node.text,
+                      node.value, false, false));
+          }
+          choosen.options.length = 0;
+      }
+
+  }
+
+  E.addListener(['add_element','remove_element',
+                 'add_all_elements','remove_all_elements'],'click',
+                 prompts_action_callback)
+
+  E.addListener(form_id,'submit',function(){
+      var choosen = D.get(selected_container);
+      for (var i = 0; i < choosen.options.length; i++) {
+          choosen.options[i].selected = 'selected';
+      }
+  });
+});
+</script>
+</%def>
--- a/rhodecode/templates/admin/users_groups/users_groups.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/admin/users_groups/users_groups.html	Sun Feb 26 17:25:09 2012 +0200
@@ -22,8 +22,8 @@
           <li>
             <span>${h.link_to(_(u'ADD NEW USER GROUP'),h.url('new_users_group'))}</span>
           </li>
-          
-        </ul>        
+
+        </ul>
     </div>
     <!-- end box / title -->
     <div class="table">
@@ -42,7 +42,7 @@
                     <td>
                         ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
                             ${h.submit('remove_','delete',id="remove_group_%s" % u_group.users_group_id,
-                            class_="delete_icon action_button",onclick="return confirm('Confirm to delete this users group');")}
+                            class_="delete_icon action_button",onclick="return  confirm('"+_('Confirm to delete this users group: %s') % u_group.users_group_name+"');")}
                         ${h.end_form()}
                     </td>
                 </tr>
--- a/rhodecode/templates/base/base.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/base/base.html	Sun Feb 26 17:25:09 2012 +0200
@@ -3,73 +3,7 @@
 
 <!-- HEADER -->
 <div id="header">
-    <!-- user -->
-    <ul id="logged-user">
-         <li class="first">
-			<div id="quick_login" style="display:none">
-			${h.form(h.url('login_home',came_from=h.url.current()))}
-			<div class="form">
-			    <div class="fields">
-			        <div class="field">
-			            <div class="label">
-			                <label for="username">${_('Username')}:</label>
-			            </div>
-			            <div class="input">
-			                ${h.text('username',class_='focus',size=40)}
-			            </div>
-			            
-			        </div>                     
-			        <div class="field">
-			            <div class="label">
-			                <label for="password">${_('Password')}:</label>
-			            </div>
-			            <div class="input">
-			                ${h.password('password',class_='focus',size=40)}
-			            </div>
-			            
-			        </div>
-			        <div class="buttons">
-			            <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
-                        <div class="register">
-                        %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
-                         ${h.link_to(_("Don't have an account ?"),h.url('register'))}
-                        %endif                        
-                        </div>
-                            ${h.submit('sign_in',_('Sign In'),class_="ui-button")}
-			        </div>
-			    </div>
-			</div>
-			${h.end_form()}
-			</div>         
-         
-             <div class="gravatar">
-                 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,20)}" />
-             </div>
-          <div class="account">
-          %if c.rhodecode_user.username == 'default':
-              <a href="${h.url('public_journal')}">${_('Public journal')}</a>   
-          %else:                        		            
-          	${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
-          %endif
-          </div>	
-         </li>
-         <li>
-            <a href="${h.url('home')}">${_('Home')}</a>
-         </li>
-         %if c.rhodecode_user.username != 'default':
-            <li>
-               <a href="${h.url('journal')}">${_('Journal')}</a> 
-               ##(${c.unread_journal}
-            </li>
-            %endif
-            %if c.rhodecode_user.username == 'default':
-                <li class="last highlight">${h.link_to(_(u'Login'),h.url('login_home'),id='quick_login_link')}</li>
-            %else:
-                <li class="last highlight">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
-            %endif
-    </ul>
-    <!-- end user -->
-    <div id="header-inner" class="title">
+    <div id="header-inner" class="title hover">
         <div id="logo">
             <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
         </div>
@@ -78,11 +12,11 @@
         <!-- END MENU -->
         ${self.body()}
     </div>
-</div>     
+</div>
 <!-- END HEADER -->
-    
+
 <!-- CONTENT -->
-<div id="content"> 
+<div id="content">
     <div class="flash_msg">
         <% messages = h.flash.pop_messages() %>
         % if messages:
@@ -92,11 +26,11 @@
             % endfor
         </ul>
         % endif
-    </div>	    
-    <div id="main"> 
+    </div>
+    <div id="main">
         ${next.main()}
     </div>
-</div> 
+</div>
 <!-- END CONTENT -->
 
 <!-- FOOTER -->
@@ -107,7 +41,7 @@
                 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
            </p>
 	       <p class="footer-link-right">
-	           <a href="${h.url('rhodecode_official')}">RhodeCode</a> 
+	           <a href="${h.url('rhodecode_official')}">RhodeCode${'-%s' % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}</a>
 	           ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
 	       </p>
        </div>
@@ -126,159 +60,143 @@
     </div>
 </%def>
 
+<%def name="usermenu()">
+  <div class="user-menu">
+      <div class="container">
+       <div class="gravatar" id="quick_login_link">
+           <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
+       </div>
+       %if c.rhodecode_user.username != 'default' and c.unread_notifications != 0:
+        <div class="notifications">
+          <a id="notification_counter" href="${h.url('notifications')}">${c.unread_notifications}</a>
+        </div>
+       %endif
+      </div>
+      <div id="quick_login" style="display:none">
+        %if c.rhodecode_user.username == 'default':
+            <h4>${_('Login to your account')}</h4>
+            ${h.form(h.url('login_home',came_from=h.url.current()))}
+            <div class="form">
+                <div class="fields">
+                    <div class="field">
+                        <div class="label">
+                            <label for="username">${_('Username')}:</label>
+                        </div>
+                        <div class="input">
+                            ${h.text('username',class_='focus',size=40)}
+                        </div>
+
+                    </div>
+                    <div class="field">
+                        <div class="label">
+                            <label for="password">${_('Password')}:</label>
+                        </div>
+                        <div class="input">
+                            ${h.password('password',class_='focus',size=40)}
+                        </div>
+
+                    </div>
+                    <div class="buttons">
+                        <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
+                        <div class="register">
+                        %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
+                         ${h.link_to(_("Don't have an account ?"),h.url('register'))}
+                        %endif
+                        </div>
+                        <div class="submit">
+                            ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
+                        </div>
+                    </div>
+                </div>
+            </div>
+            ${h.end_form()}
+        %else:
+            <div class="links_left">
+                <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
+                <div class="email">${c.rhodecode_user.email}</div>
+                <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
+                <div class="inbox"><a href="${h.url('notifications')}">${_('Inbox')}: ${c.unread_notifications}</a></div>
+            </div>
+            <div class="links_right">
+            <ol class="links">
+              <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
+              <li>${h.link_to(_(u'Journal'),h.url('journal'))}</li>
+              <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
+              <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
+            </ol>
+            </div>
+        %endif
+      </div>
+ </div>
+</%def>
 
 <%def name="menu(current=None)">
-		<% 
+		<%
 		def is_current(selected):
 			if selected == current:
 				return h.literal('class="current"')
 		%>
-		%if current not in ['home','admin']:           		
-		   ##REGULAR MENU            
+		%if current not in ['home','admin']:
+		   ##REGULAR MENU
 	        <ul id="quick">
 				<!-- repo switcher -->
 				<li>
-					<a id="repo_switcher" title="${_('Switch repository')}" href="#">
+					<a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="#">
                     <span class="icon">
                         <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
                     </span>
-                    <span>&darr;</span>					
+                    <span>&darr;</span>
 					</a>
 					<ul id="repo_switcher_list" class="repo_switcher">
                         <li>
                             <a href="#">${_('loading...')}</a>
                         </li>
 					</ul>
-					<script type="text/javascript">
-					   YUE.on('repo_switcher','mouseover',function(){
-						      function qfilter(){
-						         var S = YAHOO.util.Selector;
-						         
-						         var q_filter = YUD.get('q_filter_rs');
-						         var F = YAHOO.namespace('q_filter_rs'); 
-						         
-						         YUE.on(q_filter,'click',function(){
-						            q_filter.value = '';
-						         });
-						    
-						         F.filterTimeout = null;
-						         
-						         F.updateFilter  = function() { 
-						            // Reset timeout 
-						            F.filterTimeout = null;
-						            
-						            var obsolete = [];
-						            var nodes = S.query('ul#repo_switcher_list li a.repo_name');
-						            var req = YUD.get('q_filter_rs').value;
-						            for (n in nodes){
-						                YUD.setStyle(nodes[n].parentNode,'display','')
-						            }
-						            if (req){
-						                for (n in nodes){
-						                    if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
-						                        obsolete.push(nodes[n]); 
-						                    }
-						                }
-						                if(obsolete){
-						                    for (n in obsolete){
-						                        YUD.setStyle(obsolete[n].parentNode,'display','none');
-						                    }
-						                }
-						            }
-						         }
-						         
-						         YUE.on(q_filter,'keyup',function(e){
-						             clearTimeout(F.filterTimeout); 
-						             F.filterTimeout = setTimeout(F.updateFilter,600); 
-						         });
-						}
-						   var loaded = YUD.hasClass('repo_switcher','loaded');
-						   if(!loaded){
-							   YUD.addClass('repo_switcher','loaded');
-							   ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
-								   function(o){qfilter();},
-								   function(o){YUD.removeClass('repo_switcher','loaded');}
-								   ,null);
-						   }
-						   return false;
-					   });
-					</script>	
 				</li>
-				
+
 	            <li ${is_current('summary')}>
-	               <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
+	               <a class="menu_link" title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
 	               <span class="icon">
 	                   <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
 	               </span>
-	               <span>${_('Summary')}</span>                 
-	               </a>	            
+	               <span>${_('Summary')}</span>
+	               </a>
 	            </li>
-                ##<li ${is_current('shortlog')}>
-                ##   <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
-                ##   <span class="icon">
-                ##       <img src="${h.url('/images/icons/application_view_list.png')}" alt="${_('Shortlog')}" />
-                ##   </span>
-                ##   <span>${_('Shortlog')}</span>                 
-                ##   </a>             
-                ##</li>	            
                 <li ${is_current('changelog')}>
-                   <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
+                   <a class="menu_link" title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
                    <span class="icon">
                        <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
                    </span>
-                   <span>${_('Changelog')}</span>                 
-                   </a>             
-                </li>   	
-                
+                   <span>${_('Changelog')}</span>
+                   </a>
+                </li>
+
                 <li ${is_current('switch_to')}>
-                   <a title="${_('Switch to')}" href="#">
+                   <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
                    <span class="icon">
                        <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
                    </span>
-                   <span>${_('Switch to')}</span>                 
-                   </a>    
-                    <ul>
-                        <li>
-                            ${h.link_to('%s (%s)' % (_('branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
-                            <ul>
-                            %if c.rhodecode_repo.branches.values():
-						        %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
-						            <li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
-						        %endfor
-						    %else:
-						    	<li>${h.link_to(_('There are no branches yet'),'#')}</li>
-						    %endif
-                            </ul>                        
-                        </li>
-                        <li>
-                            ${h.link_to('%s (%s)' % (_('tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
-                            <ul>
-                            %if c.rhodecode_repo.tags.values():
-                                %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
-                                 <li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
-                                %endfor
-                            %else:
-                            	<li>${h.link_to(_('There are no tags yet'),'#')}</li>
-                            %endif
-                            </ul>                        
-                        </li>                        
+                   <span>${_('Switch to')}</span>
+                   </a>
+                    <ul id="switch_to_list" class="switch_to">
+                        <li><a href="#">${_('loading...')}</a></li>
                     </ul>
                 </li>
                 <li ${is_current('files')}>
-                   <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
+                   <a class="menu_link" title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
                    <span class="icon">
                        <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
                    </span>
-                   <span>${_('Files')}</span>                 
-                   </a>             
-                </li>                            
-				
+                   <span>${_('Files')}</span>
+                   </a>
+                </li>
+
                 <li ${is_current('options')}>
-                   <a title="${_('Options')}" href="#">
+                   <a class="menu_link" title="${_('Options')}" href="#">
                    <span class="icon">
                        <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
                    </span>
-                   <span>${_('Options')}</span>                 
+                   <span>${_('Options')}</span>
                    </a>
                    <ul>
                    %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
@@ -290,10 +208,10 @@
                    %endif
                    	<li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
                    	<li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
-                    
+
                     % if h.HasPermissionAll('hg.admin')('access admin main page'):
                      <li>
-                       ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}  
+                       ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
                         <%def name="admin_menu()">
                         <ul>
                             <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
@@ -303,18 +221,18 @@
                             <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
                             <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
                             <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
-                            <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>        
+                            <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
                         </ul>
                         </%def>
-                        
+
                         ${admin_menu()}
                      </li>
                     % endif
-                   </ul>             
+                   </ul>
                 </li>
-                
+
                 <li>
-                    <a title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
+                    <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
                     <span class="icon_short">
                         <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
                     </span>
@@ -322,56 +240,99 @@
                     </a>
                 </li>
                 <li>
-                    <a title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
+                    <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
                     <span class="icon_short">
                         <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
                     </span>
                     <span class="short">${c.repository_forks}</span>
                     </a>
-                </li>                
-                
+                </li>
+                ${usermenu()}
 	        </ul>
+            <script type="text/javascript">
+               YUE.on('repo_switcher','mouseover',function(){
+                      function qfilter(){
+                          var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
+                          var target = 'q_filter_rs';
+                          var func = function(node){
+                              return node.parentNode;
+                          }
+                          q_filter(target,nodes,func);
+                      }
+                   var loaded = YUD.hasClass('repo_switcher','loaded');
+                   if(!loaded){
+                       YUD.addClass('repo_switcher','loaded');
+                       ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
+                           function(o){qfilter();},
+                           function(o){YUD.removeClass('repo_switcher','loaded');}
+                           ,null);
+                   }
+                   return false;
+               });
+
+              YUE.on('branch_tag_switcher','mouseover',function(){
+                 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
+                 if(!loaded){
+                     YUD.addClass('branch_tag_switcher','loaded');
+                     ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
+                         function(o){},
+                         function(o){YUD.removeClass('branch_tag_switcher','loaded');}
+                         ,null);
+                 }
+                 return false;
+              });
+            </script>
 		%else:
 		    ##ROOT MENU
             <ul id="quick">
                 <li>
-                    <a title="${_('Home')}"  href="${h.url('home')}">
+                    <a class="menu_link" title="${_('Home')}"  href="${h.url('home')}">
                     <span class="icon">
                         <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
                     </span>
-                    <span>${_('Home')}</span>                 
-                    </a>        
+                    <span>${_('Home')}</span>
+                    </a>
                 </li>
-                % if c.rhodecode_user.username != 'default':
+                %if c.rhodecode_user.username != 'default':
                  <li>
-                    <a title="${_('Journal')}"  href="${h.url('journal')}">
+                    <a class="menu_link" title="${_('Journal')}"  href="${h.url('journal')}">
                     <span class="icon">
                         <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
                     </span>
-                    <span>${_('Journal')}</span>                 
-                    </a>        
+                    <span>${_('Journal')}</span>
+                    </a>
                  </li>
-                % endif
+                %else:
+                 <li>
+                    <a class="menu_link" title="${_('Public journal')}"  href="${h.url('public_journal')}">
+                    <span class="icon">
+                        <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
+                    </span>
+                    <span>${_('Public journal')}</span>
+                    </a>
+                 </li>
+                %endif
                 <li>
-                    <a title="${_('Search')}"  href="${h.url('search')}">
+                    <a class="menu_link" title="${_('Search')}"  href="${h.url('search')}">
                     <span class="icon">
                         <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
                     </span>
-                    <span>${_('Search')}</span>                 
-                    </a>        
+                    <span>${_('Search')}</span>
+                    </a>
                 </li>
-                
+
 				%if h.HasPermissionAll('hg.admin')('access admin main page'):
                 <li ${is_current('admin')}>
-                   <a title="${_('Admin')}" href="${h.url('admin_home')}">
+                   <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
                    <span class="icon">
                        <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
                    </span>
-                   <span>${_('Admin')}</span>                 
+                   <span>${_('Admin')}</span>
                    </a>
                     ${admin_menu()}
                 </li>
 				%endif
+                ${usermenu()}
 			</ul>
-		%endif    
+		%endif
 </%def>
--- a/rhodecode/templates/base/root.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/base/root.html	Sun Feb 26 17:25:09 2012 +0200
@@ -11,22 +11,21 @@
         <%def name="css()">
             <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen"/>
             <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css')}"/>
-            <link rel="stylesheet" type="text/css" href="${h.url('/css/diff.css')}"/>
             ## EXTRA FOR CSS
             ${self.css_extra()}
         </%def>
         <%def name="css_extra()">
         </%def>
-                    
+
         ${self.css()}
-        
+
         %if c.ga_code:
         <!-- Analytics -->
 	     <script type="text/javascript">
 	      var _gaq = _gaq || [];
 	      _gaq.push(['_setAccount', '${c.ga_code}']);
 	      _gaq.push(['_trackPageview']);
-	    
+
 	      (function() {
 	        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
 	        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
@@ -34,7 +33,7 @@
 	      })();
 	     </script>
 	    %endif
-        
+
         ## JAVASCRIPT ##
         <%def name="js()">
             <script type="text/javascript" src="${h.url('/js/yui.2.9.js')}"></script>
@@ -45,17 +44,21 @@
             <script type="text/javascript" src="${h.url('/js/rhodecode.js')}"></script>
            ## EXTRA FOR JS
            ${self.js_extra()}
-        
+
             <script type="text/javascript">
-            var base_url  = "${h.url('toggle_following')}";
-            function onSuccess(target){
+            var follow_base_url  = "${h.url('toggle_following')}";
+            var stop_follow_text = "${_('Stop following this repository')}";
+            var start_follow_text = "${_('Start following this repository')}";
+
+
+            var onSuccessFollow = function(target){
                 var f = YUD.get(target.id);
                 var f_cnt = YUD.get('current_followers_count');
-                
+
                 if(f.getAttribute('class')=='follow'){
                     f.setAttribute('class','following');
-                    f.setAttribute('title',"${_('Stop following this repository')}");
-                    
+                    f.setAttribute('title',stop_follow_text);
+
                     if(f_cnt){
                         var cnt = Number(f_cnt.innerHTML)+1;
                         f_cnt.innerHTML = cnt;
@@ -63,46 +66,50 @@
                 }
                 else{
                     f.setAttribute('class','follow');
-                    f.setAttribute('title',"${_('Start following this repository')}");
+                    f.setAttribute('title',start_follow_text);
                     if(f_cnt){
                         var cnt = Number(f_cnt.innerHTML)+1;
                         f_cnt.innerHTML = cnt;
-                    }      
+                    }
                 }
             }
-            
-            function toggleFollowingUser(target,fallows_user_id,token,user_id){
+
+            var toggleFollowingUser = function(target,fallows_user_id,token,user_id){
                 args = 'follows_user_id='+fallows_user_id;
                 args+= '&amp;auth_token='+token;
                 if(user_id != undefined){
                     args+="&amp;user_id="+user_id;
                 }
-                YUC.asyncRequest('POST',base_url,{
+                YUC.asyncRequest('POST',follow_base_url,{
                     success:function(o){
-                        onSuccess(target);
+                    	onSuccessFollow(target);
                     }
                 },args);
                 return false;
             }
-            
-            function toggleFollowingRepo(target,fallows_repo_id,token,user_id){
-            
+
+            var toggleFollowingRepo = function(target,fallows_repo_id,token,user_id){
+
                 args = 'follows_repo_id='+fallows_repo_id;
                 args+= '&amp;auth_token='+token;
                 if(user_id != undefined){
                     args+="&amp;user_id="+user_id;
-                }    
-                YUC.asyncRequest('POST',base_url,{
+                }
+                YUC.asyncRequest('POST',follow_base_url,{
                     success:function(o){
-                        onSuccess(target);
+                    	onSuccessFollow(target);
                     }
-                },args); 
+                },args);
                 return false;
             }
            YUE.onDOMReady(function(){
-               
+             tooltip_activate();
+             show_more_event();
+
              YUE.on('quick_login_link','click',function(e){
-                 
+                 // make sure we don't redirect
+                 YUE.preventDefault(e);
+
                  if(YUD.hasClass('quick_login_link','enabled')){
                      YUD.setStyle('quick_login','display','none');
                      YUD.removeClass('quick_login_link','enabled');
@@ -110,35 +117,19 @@
                  else{
                      YUD.setStyle('quick_login','display','');
                      YUD.addClass('quick_login_link','enabled');
-                     YUD.get('username').focus();
+                     var usr = YUD.get('username');
+                     if(usr){
+                    	 usr.focus();
+                     }
                  }
-                 //make sure we don't redirect 
-                 YUE.preventDefault(e);
              });
-               
-            tooltip_activate();
-            show_more_event();
-            
-            YUE.on(YUQ('.quick_repo_menu'),'click',function(e){
-            	var menu = e.currentTarget.firstElementChild;
-            	if(YUD.hasClass(menu,'hidden')){
-            		YUD.addClass(e.currentTarget,'active');
-            		YUD.removeClass(menu,'hidden');
-            	}else{
-            		YUD.removeClass(e.currentTarget,'active');
-            		YUD.addClass(menu,'hidden');
-            	}
-            })
-            
-           })   
+           })
             </script>
-        
         </%def>
-        <%def name="js_extra()">
-        </%def>      
+        <%def name="js_extra()"></%def>
         ${self.js()}
     </head>
     <body id="body">
         ${next.body()}
     </body>
-</html>
\ No newline at end of file
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/bookmarks/bookmarks.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,78 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${c.repo_name} ${_('Bookmarks')} - ${c.rhodecode_name}
+</%def>
+
+
+<%def name="breadcrumbs_links()">
+    <input class="q_filter_box" id="q_filter_bookmarks" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
+    ${h.link_to(u'Home',h.url('/'))}
+    &raquo;
+    ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
+    &raquo;
+    ${_('bookmarks')}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('bookmarks')}
+</%def>
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+    <!-- end box / title -->
+    <div class="table">
+        <%include file='bookmarks_data.html'/>
+    </div>
+</div>
+<script type="text/javascript">
+
+// main table sorting
+var myColumnDefs = [
+    {key:"name",label:"${_('Name')}",sortable:true},
+    {key:"date",label:"${_('Date')}",sortable:true,
+        sortOptions: { sortFunction: dateSort }},
+    {key:"author",label:"${_('Author')}",sortable:true},
+    {key:"revision",label:"${_('Revision')}",sortable:true,
+        sortOptions: { sortFunction: revisionSort }},
+];
+
+var myDataSource = new YAHOO.util.DataSource(YUD.get("bookmarks_data"));
+
+myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
+
+myDataSource.responseSchema = {
+    fields: [
+        {key:"name"},
+        {key:"date"},
+        {key:"author"},
+        {key:"revision"},
+    ]
+};
+
+var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
+        {
+         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...')}",
+        }
+);
+myDataTable.subscribe('postRenderEvent',function(oArgs) {
+    tooltip_activate();
+    var func = function(node){
+        return node.parentNode.parentNode.parentNode.parentNode.parentNode;
+    }
+    q_filter('q_filter_bookmarks',YUQ('div.table tr td .logbooks .bookbook a'),func);
+});
+
+</script>
+
+
+</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/bookmarks/bookmarks_data.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,33 @@
+%if c.repo_bookmarks:
+   <div id="table_wrap" class="yui-skin-sam">
+    <table id="bookmarks_data">
+      <thead>
+    	<tr>
+            <th class="left">${_('Name')}</th>
+            <th class="left">${_('Date')}</th>
+            <th class="left">${_('Author')}</th>
+            <th class="left">${_('Revision')}</th>
+    	</tr>
+      </thead>
+		%for cnt,book in enumerate(c.repo_bookmarks.items()):
+		<tr class="parity${cnt%2}">
+            <td>
+                <span class="logbooks">
+                    <span class="bookbook">${h.link_to(book[0],
+                    h.url('files_home',repo_name=c.repo_name,revision=book[1].raw_id))}</span>
+                </span>
+            </td>
+            <td><span class="tooltip" title="${h.age(book[1].date)}">${book[1].date}</span></td>
+	        <td title="${book[1].author}">${h.person(book[1].author)}</td>
+	        <td>
+              <div>
+                  <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=book[1].raw_id)}">r${book[1].revision}:${h.short_id(book[1].raw_id)}</a></pre>
+              </div>
+            </td>
+		</tr>
+		%endfor
+    </table>
+    </div>
+%else:
+	${_('There are no bookmarks yet')}
+%endif
--- a/rhodecode/templates/branches/branches.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/branches/branches.html	Sun Feb 26 17:25:09 2012 +0200
@@ -6,15 +6,16 @@
 </%def>
 
 <%def name="breadcrumbs_links()">
+    <input class="q_filter_box" id="q_filter_branches" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
     ${h.link_to(u'Home',h.url('/'))}
-    &raquo; 
+    &raquo;
     ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
     &raquo;
     ${_('branches')}
 </%def>
 
 <%def name="page_nav()">
-    ${self.menu('branches')} 
+    ${self.menu('branches')}
 </%def>
 
 <%def name="main()">
@@ -27,5 +28,50 @@
     <div class="table">
         <%include file='branches_data.html'/>
     </div>
-</div>    
-</%def>     
\ No newline at end of file
+</div>
+<script type="text/javascript">
+
+// main table sorting
+var myColumnDefs = [
+    {key:"name",label:"${_('Name')}",sortable:true},
+    {key:"date",label:"${_('Date')}",sortable:true,
+        sortOptions: { sortFunction: dateSort }},
+    {key:"author",label:"${_('Author')}",sortable:true},
+    {key:"revision",label:"${_('Revision')}",sortable:true,
+        sortOptions: { sortFunction: revisionSort }},
+];
+
+var myDataSource = new YAHOO.util.DataSource(YUD.get("branches_data"));
+
+myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
+
+myDataSource.responseSchema = {
+    fields: [
+        {key:"name"},
+        {key:"date"},
+        {key:"author"},
+        {key:"revision"},
+    ]
+};
+
+var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
+        {
+         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...')}",
+        }
+);
+myDataTable.subscribe('postRenderEvent',function(oArgs) {
+    tooltip_activate();
+    var func = function(node){
+        return node.parentNode.parentNode.parentNode.parentNode.parentNode;
+    }
+    q_filter('q_filter_branches',YUQ('div.table tr td .logtags .branchtag a'),func);
+});
+
+</script>
+
+</%def>
--- a/rhodecode/templates/branches/branches_data.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/branches/branches_data.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,53 +1,52 @@
-% if c.repo_branches:
-    <table class="table_disp">
+%if c.repo_branches:
+   <div id="table_wrap" class="yui-skin-sam">
+    <table id="branches_data">
+      <thead>
         <tr>
+            <th class="left">${_('name')}</th>
             <th class="left">${_('date')}</th>
-            <th class="left">${_('name')}</th>
             <th class="left">${_('author')}</th>
             <th class="left">${_('revision')}</th>
-            <th class="left">${_('links')}</th>
         </tr>
+      </thead>
 		%for cnt,branch in enumerate(c.repo_branches.items()):
 		<tr class="parity${cnt%2}">
-            <td><span class="tooltip" title="${h.age(branch[1].date)}">${branch[1].date}</span>
-            </td>
             <td>
                 <span class="logtags">
                     <span class="branchtag">${h.link_to(branch[0],
-                    h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
-                </span>         
-            </td>		
+                    h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
+                </span>
+            </td>
+            <td><span class="tooltip" title="${h.age(branch[1].date)}">${branch[1].date}</span></td>
             <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
-            <td>r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</td>
-			<td class="nowrap">
-			${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
-			|
-			${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
-			</td>
-		</tr>	
+            <td>
+                <div>
+                    <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre>
+                </div>
+            </td>
+		</tr>
 		%endfor
         % if hasattr(c,'repo_closed_branches') and c.repo_closed_branches:
           %for cnt,branch in enumerate(c.repo_closed_branches.items()):
           <tr class="parity${cnt%2}">
-              <td><span class="tooltip" title="${h.age(branch[1].date)}">${branch[1].date}</span>
-              </td>
               <td>
                   <span class="logtags">
                       <span class="branchtag">${h.link_to(branch[0]+' [closed]',
                       h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
-                  </span>         
-              </td>       
+                  </span>
+              </td>
+              <td><span class="tooltip" title="${h.age(branch[1].date)}">${branch[1].date}</span></td>
               <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
-              <td>r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</td>
-              <td class="nowrap">
-              ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
-              |
-              ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
+              <td>
+                <div>
+                    <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre>
+                </div>
               </td>
-          </tr>   
+          </tr>
           %endfor
-        %endif  
+        %endif
     </table>
+    </div>
 %else:
     ${_('There are no branches yet')}
-%endif
\ No newline at end of file
+%endif
--- a/rhodecode/templates/changelog/changelog.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/changelog/changelog.html	Sun Feb 26 17:25:09 2012 +0200
@@ -11,11 +11,11 @@
     &raquo;
     ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
     &raquo;
-    ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}  
+    ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
 </%def>
 
 <%def name="page_nav()">
-	${self.menu('changelog')}     
+	${self.menu('changelog')}
 </%def>
 
 <%def name="main()">
@@ -33,52 +33,65 @@
 				<div id="graph_content">
 					<div class="container_header">
 				        ${h.form(h.url.current(),method='get')}
-				        <div class="info_box">
-				          ${h.submit('set',_('Show'),class_="ui-button-small")}
+				        <div class="info_box" style="float:left">
+				          ${h.submit('set',_('Show'),class_="ui-btn")}
 				          ${h.text('size',size=1,value=c.size)}
-				          <span class="rev">${_('revisions')}</span>
+				          ${_('revisions')}
 				        </div>
 				        ${h.end_form()}
 					<div id="rev_range_container" style="display:none"></div>
+                    <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
 					</div>
-					
+
 				%for cnt,cs in enumerate(c.pagination):
-					<div id="chg_${cnt+1}" class="container">
+					<div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
 						<div class="left">
-							<div class="date">
+							<div>
 							${h.checkbox(cs.short_id,class_="changeset_range")}
-							<span>${_('commit')} ${cs.revision}: ${h.short_id(cs.raw_id)}@${cs.date}</span>
+							<span class="tooltip" title="${h.age(cs.date)}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
 							</div>
 							<div class="author">
 								<div class="gravatar">
 									<img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),16)}"/>
 								</div>
-								<div title="${h.email_or_none(cs.author)}" class="user">${h.person(cs.author)}</div>
-								##<span><a href="mailto:${h.email_or_none(cs.author)}">${h.email_or_none(cs.author)}</a></span><br/>
+								<div title="${cs.author}" class="user">${h.person(cs.author)}</div>
 							</div>
-							<div class="message">${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
-						</div>	
+                            <div class="date">${cs.date}</div>
+						</div>
+						<div class="mid">
+                            <div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
+                            <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
+						</div>
 						<div class="right">
 									<div id="${cs.raw_id}_changes_info" class="changes">
-                                        <span id="${cs.raw_id}" class="changed_total tooltip" title="${_('Affected number of files, click to show more details')}">${len(cs.affected_files)}</span>
-									</div>					
-										%if len(cs.parents)>1:
-										<div class="merge">
-											${_('merge')}<img alt="merge" src="${h.url('/images/icons/arrow_join.png')}"/>
-										</div>
-										%endif
-								   %if cs.parents:							
+                                        <div id="${cs.raw_id}"  style="float:right;" class="changed_total tooltip" title="${_('Affected number of files, click to show more details')}">${len(cs.affected_files)}</div>
+                                        <div class="comments-container">
+                                        %if len(c.comments.get(cs.raw_id,[])) > 0:
+                                            <div class="comments-cnt" title="${('comments')}">
+                                              <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
+                                               <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
+                                               <img src="${h.url('/images/icons/comments.png')}">
+                                              </a>
+                                            </div>
+                                        %endif
+                                        </div>
+									</div>
+								   %if cs.parents:
 									%for p_cs in reversed(cs.parents):
-										<div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(h.short_id(p_cs.raw_id),
-											h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
+										<div class="parent">${_('Parent')}
+											<span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
+											h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
 										</div>
 									%endfor
-								   %else:	
-                                        <div class="parent">${_('No parents')}</div>   
-                                   %endif  
-                    									
+								   %else:
+                                        <div class="parent">${_('No parents')}</div>
+                                   %endif
+
 								<span class="logtags">
-									%if cs.branch:
+									%if len(cs.parents)>1:
+									<span class="merge">${_('merge')}</span>
+									%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>
 									%endif
@@ -86,26 +99,26 @@
 										<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>
 									%endfor
-								</span>																	
-						</div>				
+								</span>
+						</div>
 					</div>
-					
+
 				%endfor
 				<div class="pagination-wh pagination-left">
 					${c.pagination.pager('$link_previous ~2~ $link_next')}
-				</div>			
+				</div>
 				</div>
 			</div>
-			
+
 			<script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
 			<script type="text/javascript">
 				YAHOO.util.Event.onDOMReady(function(){
-					
+
                     //Monitor range checkboxes and build a link to changesets
-                    //ranges 
+                    //ranges
                     var checkboxes = YUD.getElementsByClassName('changeset_range');
                     var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
-                    YUE.on(checkboxes,'click',function(e){      
+                    YUE.on(checkboxes,'click',function(e){
                         var checked_checkboxes = [];
                         for (pos in checkboxes){
                             if(checkboxes[pos].checked){
@@ -115,10 +128,10 @@
                         if(checked_checkboxes.length>1){
                         	var rev_end = checked_checkboxes[0].name;
                         	var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
-                        	
+
                             var url = url_tmpl.replace('__REVRANGE__',
                             		rev_start+'...'+rev_end);
-                            
+
                         var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
                         link = link.replace('__S',rev_start);
                         link = link.replace('__E',rev_end);
@@ -127,19 +140,60 @@
                         }
                         else{
                         	YUD.setStyle('rev_range_container','display','none');
-                        	
+
                         }
-                    });					
-					
-                    //Fetch changeset details 
+                    });
+
+                    var msgs = YUQ('.message');
+                    // get first element height
+                    var el = YUQ('#graph_content .container')[0];
+                    var row_h = el.clientHeight;
+                    for(var i=0;i<msgs.length;i++){
+                    	var m = msgs[i];
+
+                    	var h = m.clientHeight;
+                    	var pad = YUD.getStyle(m,'padding');
+                    	if(h > row_h){
+                    		var offset = row_h - (h+12);
+                    		YUD.setStyle(m.nextElementSibling,'display','block');
+                    		YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
+                    	};
+                    }
+                    YUE.on(YUQ('.expand'),'click',function(e){
+                    	var elem = e.currentTarget.parentNode.parentNode;
+                    	YUD.setStyle(e.currentTarget,'display','none');
+                    	YUD.setStyle(elem,'height','auto');
+
+                    	//redraw the graph, max_w and jsdata are global vars
+                        set_canvas(max_w);
+
+                        var r = new BranchRenderer();
+                        r.render(jsdata,max_w);
+
+                    })
+
+                    // Fetch changeset details
                     YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
                     	var id = e.currentTarget.id
                     	var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}"
                     	var url = url.replace('__CS__',id);
                     	ypjax(url,id+'_changes_info',function(){tooltip_activate()});
                     });
-                    
-                    
+
+                    // change branch filter
+                    YUE.on(YUD.get('branch_filter'),'change',function(e){
+                    	var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
+                    	var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
+                    	var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
+                    	var url = url.replace('__BRANCH__',selected_branch);
+                    	if(selected_branch != ''){
+                    		window.location = url;
+                    	}else{
+                    		window.location = url_main;
+                    	}
+
+                    });
+
 					function set_canvas(heads) {
 						var c = document.getElementById('graph_nodes');
 						var t = document.getElementById('graph_content');
@@ -153,7 +207,7 @@
 					var heads = 1;
 					var max_heads = 0;
 					var jsdata = ${c.jsdata|n};
-					
+
 					for( var i=0;i<jsdata.length;i++){
 					    var m = Math.max.apply(Math, jsdata[i][1]);
 					    if (m>max_heads){
@@ -162,15 +216,15 @@
 					}
 					var max_w = Math.max(100,max_heads*25);
 					set_canvas(max_w);
-					
+
 					var r = new BranchRenderer();
 					r.render(jsdata,max_w);
-					
+
 				});
 			</script>
 		%else:
 			${_('There are no changes yet')}
-		%endif  
+		%endif
     </div>
-</div>    
-</%def>
\ No newline at end of file
+</div>
+</%def>
--- a/rhodecode/templates/changelog/changelog_details.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/changelog/changelog_details.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,9 +1,9 @@
-% if len(c.cs.affected_files) <= c.affected_files_cut_off:                     
+% if len(c.cs.affected_files) <= c.affected_files_cut_off:
 <span class="removed tooltip" title="<b>${_('removed')}</b>${h.changed_tooltip(c.cs.removed)}">${len(c.cs.removed)}</span>
 <span class="changed tooltip" title="<b>${_('changed')}</b>${h.changed_tooltip(c.cs.changed)}">${len(c.cs.changed)}</span>
 <span class="added tooltip" title="<b>${_('added')}</b>${h.changed_tooltip(c.cs.added)}">${len(c.cs.added)}</span>
 % else:
  <span class="removed tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
  <span class="changed tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
- <span class="added tooltip"   title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>                                         
-% endif
\ No newline at end of file
+ <span class="added tooltip"   title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
+% endif
--- a/rhodecode/templates/changeset/changeset.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/changeset/changeset.html	Sun Feb 26 17:25:09 2012 +0200
@@ -11,11 +11,11 @@
     &raquo;
     ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
     &raquo;
-    ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
+    ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
 </%def>
 
 <%def name="page_nav()">
-    ${self.menu('changelog')}     
+    ${self.menu('changelog')}
 </%def>
 
 <%def name="main()">
@@ -27,19 +27,24 @@
     <div class="table">
 		<div class="diffblock">
 			<div class="code-header">
-				<div>
-				${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
-				 &raquo; <span>${h.link_to(_('raw diff'),
-				h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show'))}</span>
-				 &raquo; <span>${h.link_to(_('download diff'),
-				h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download'))}</span>
-				</div>
+                <div class="hash">
+                 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
+                </div>
+                <div class="date">
+                  ${c.changeset.date}
+                </div>
+                <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()}
+                </div>
+                <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
 			</div>
 		</div>
 	    <div id="changeset_content">
 			<div class="container">
 	             <div class="left">
-	                 <div class="date">${_('commit')} ${c.changeset.revision}: ${h.short_id(c.changeset.raw_id)}@${c.changeset.date}</div>
 	                 <div class="author">
 	                     <div class="gravatar">
 	                         <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
@@ -47,93 +52,141 @@
 	                     <span>${h.person(c.changeset.author)}</span><br/>
 	                     <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
 	                 </div>
-	                 <div class="message">${h.link_to(h.wrap_paragraphs(c.changeset.message),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</div>
+	                 <div class="message">${h.urlify_commit(h.wrap_paragraphs(c.changeset.message),c.repo_name)}</div>
 	             </div>
 	             <div class="right">
 		             <div class="changes">
-                        % if len(c.changeset.affected_files) <= c.affected_files_cut_off:	             
+                        % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
 		                 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
 		                 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
 		                 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
 	                    % else:
                          <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
                          <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
-                         <span class="added"   title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>	                    
-	                    % endif		                 
-		             </div>                  
-		                 %if len(c.changeset.parents)>1:
-		                 <div class="merge">
-		                     ${_('merge')}<img alt="merge" src="${h.url("/images/icons/arrow_join.png")}"/>
-		                 </div>
-		                 %endif
-		                 
+                         <span class="added"   title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
+	                    % endif
+		             </div>
+
 		            %if c.changeset.parents:
 		             %for p_cs in reversed(c.changeset.parents):
-		                 <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(h.short_id(p_cs.raw_id),
-		                     h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
+		                 <div class="parent">${_('Parent')}
+                     <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
+		                     h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
 		                 </div>
 		             %endfor
-                    %else: 
-                        <div class="parent">${_('No parents')}</div>   
-                    %endif		             
+                    %else:
+                        <div class="parent">${_('No parents')}</div>
+                    %endif
 		         <span class="logtags">
+                 %if len(c.changeset.parents)>1:
+                 <span class="merge">${_('merge')}</span>
+                 %endif
 		             <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
 		             ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
 		             %for tag in c.changeset.tags:
 		                 <span class="tagtag"  title="${'%s %s' % (_('tag'),tag)}">
 		                 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
 		             %endfor
-		         </span>                                                                 
-	                </div>              
+		         </span>
+	                </div>
 	        </div>
-	        <span style="font-size:1.1em;font-weight: bold">
-	        ${_('%s files affected with %s additions and %s deletions.') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
+	        <span>
+	        ${_('%s files affected with %s additions 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">${h.link_to(h.safe_unicode(filenode.path),
-		                                        h.url.current(anchor=h.repo_name_slug('C%s' % h.safe_unicode(filenode.path))))}</div>
+                            <div class="node">
+                            %if change != 'removed':
+                                ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path)+"_target")}
+                            %else:
+                                ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
+                            %endif
+                            </div>
 		                    <div class="changes">${h.fancy_file_stats(stat)}</div>
 	                    </div>
 	                %endfor
 	                % if c.cut_off:
 	                  ${_('Changeset was too big and was cut off...')}
 	                % endif
-	        </div>         
+	        </div>
 	    </div>
-	    
+
     </div>
-    	
-	%for change,filenode,diff,cs1,cs2,stat in c.changes:
-		%if change !='removed':
-		<div style="clear:both;height:10px"></div>
-		<div class="diffblock">
-			<div id="${h.repo_name_slug('C%s' % h.safe_unicode(filenode.path))}" class="code-header">
-				<div class="changeset_header">
-					<span class="changeset_file">
-						${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
-						revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
-					</span>
-					%if 1:
-					&raquo; <span>${h.link_to(_('diff'),
-					h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff'))}</span>
-					&raquo; <span>${h.link_to(_('raw diff'),
-					h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw'))}</span>
-					&raquo; <span>${h.link_to(_('download diff'),
-					h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download'))}</span>
-					%endif
-				</div>
-			</div>
-			<div class="code-body">        
-					%if diff:
-						${diff|n}
-					%else:
-						${_('No changes in this file')}
-					%endif
-			</div>
-		</div>
-		%endif
-	%endfor 
-    </div>	
+
+    ## diff block
+    <%namespace name="diff_block" file="/changeset/diff_block.html"/>
+    ${diff_block.diff_block(c.changes)}
+
+    ## template for inline comment form
+    <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
+    ${comment.comment_inline_form(c.changeset)}
+
+    ${comment.comments(c.changeset)}
+
+    <script type="text/javascript">
+      var deleteComment = function(comment_id){
+
+          var url = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}".replace('__COMMENT_ID__',comment_id);
+          var postData = '_method=delete';
+          var success = function(o){
+              var n = YUD.get('comment-'+comment_id);
+              n.parentNode.removeChild(n);
+          }
+          ajaxPOST(url,postData,success);
+      }
+
+      YUE.onDOMReady(function(){
+
+          YUE.on(YUQ('.show-inline-comments'),'change',function(e){
+              var show = 'none';
+              var target = e.currentTarget;
+              if(target.checked){
+                  var show = ''
+              }
+              var boxid = YUD.getAttribute(target,'id_for');
+              var comments = YUQ('#{0} .inline-comments'.format(boxid));
+              for(c in comments){
+                 YUD.setStyle(comments[c],'display',show);
+              }
+              var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
+              for(c in btns){
+                  YUD.setStyle(btns[c],'display',show);
+               }
+          })
+
+          YUE.on(YUQ('.line'),'click',function(e){
+              var tr = e.currentTarget;
+              injectInlineForm(tr);
+          });
+
+          // inject comments into they proper positions
+          var file_comments = YUQ('.inline-comment-placeholder');
+
+          for (f in file_comments){
+              var box = file_comments[f];
+              var inlines = box.children;
+              for(var i=0; i<inlines.length; i++){
+                  try{
+
+                    var inline = inlines[i];
+                    var lineno = YUD.getAttribute(inlines[i],'line');
+                    var lineid = "{0}_{1}".format(YUD.getAttribute(inline,'target_id'),lineno);
+                    var target_line = YUD.get(lineid);
+
+                    var add = createInlineAddButton(target_line.parentNode,'${_("add another comment")}');
+                    YUD.insertAfter(add,target_line.parentNode);
+
+                    var comment = new YAHOO.util.Element(tableTr('inline-comments',inline.innerHTML))
+                    YUD.insertAfter(comment,target_line.parentNode);
+                  }catch(e){
+                	  console.log(e);
+                  }
+              }
+          }
+      })
+
+    </script>
+
+    </div>
 </%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/changeset/changeset_file_comment.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,105 @@
+## -*- coding: utf-8 -*-
+## usage:
+## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
+## ${comment.comment_block(co)}
+##
+<%def name="comment_block(co)">
+  <div class="comment" id="comment-${co.comment_id}">
+    <div class="comment-wrapp">
+  	<div class="meta">
+  		<span class="user">
+  			<img src="${h.gravatar_url(co.author.email, 20)}" />
+  			${co.author.username}
+  		</span>
+  		<span class="date">
+  			${h.age(co.modified_at)}
+  		</span>
+      %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
+        <span class="buttons">
+          <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
+        </span>
+      %endif
+  	</div>
+  	<div class="text">
+  		${h.rst_w_mentions(co.text)|n}
+  	</div>
+    </div>
+  </div>
+</%def>
+
+
+<%def name="comment_inline_form(changeset)">
+<div id='comment-inline-form-template' style="display:none">
+  <div class="comment-inline-form">
+  %if c.rhodecode_user.username != 'default':
+      ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id))}
+      <div class="clearfix">
+          <div class="comment-help">${_('Commenting on line')} {1}. ${_('Comments parsed using')}
+          <a href="${h.url('rst_help')}">RST</a> ${_('syntax')} ${_('with')}
+          <span style="color:#003367" class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> ${_('support')}
+          </div>
+          <textarea id="text_{1}" name="text"></textarea>
+      </div>
+      <div class="comment-button">
+      <input type="hidden" name="f_path" value="{0}">
+      <input type="hidden" name="line" value="{1}">
+      ${h.submit('save', _('Comment'), class_='ui-btn')}
+      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
+      </div>
+      ${h.end_form()}
+  %else:
+      ${h.form('')}
+      <div class="clearfix">
+          <div class="comment-help">
+            ${'You need to be logged in to comment.'} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
+          </div>
+      </div>
+      <div class="comment-button">
+      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
+      </div>
+      ${h.end_form()}
+  %endif
+  </div>
+</div>
+</%def>
+
+
+<%def name="comments(changeset)">
+
+<div class="comments">
+    <div class="comments-number">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
+
+    %for path, lines in c.inline_comments:
+        <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.FID(changeset.raw_id,path)}">
+        % for line,comments in lines.iteritems():
+            <div class="inline-comment-placeholder-line" line="${line}" target_id="${h.safeid(h.safe_unicode(path))}">
+            %for co in comments:
+                ${comment_block(co)}
+            %endfor
+            </div>
+        %endfor
+        </div>
+    %endfor
+
+    %for co in c.comments:
+        ${comment_block(co)}
+    %endfor
+    %if c.rhodecode_user.username != 'default':
+    <div class="comment-form">
+        ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id))}
+        <strong>${_('Leave a comment')}</strong>
+        <div class="clearfix">
+            <div class="comment-help">
+                ${_('Comments parsed using')} <a href="${h.url('rst_help')}">RST</a> ${_('syntax')}
+                ${_('with')} <span style="color:#003367" class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> ${_('support')}
+            </div>
+                ${h.textarea('text')}
+        </div>
+        <div class="comment-button">
+        ${h.submit('save', _('Comment'), class_='ui-button')}
+        </div>
+        ${h.end_form()}
+    </div>
+    %endif
+</div>
+</%def>
--- a/rhodecode/templates/changeset/changeset_range.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/changeset/changeset_range.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,3 +1,4 @@
+## -*- coding: utf-8 -*-
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
@@ -13,7 +14,7 @@
 </%def>
 
 <%def name="page_nav()">
-    ${self.menu('changelog')}     
+    ${self.menu('changelog')}
 </%def>
 
 <%def name="main()">
@@ -24,27 +25,23 @@
     </div>
     <div class="table">
 		<div id="body" class="diffblock">
-			<div class="code-header">
-				<div>
+			<div class="code-header cv">
+		        <h3 class="code-header-title">${_('Compare View')}</h3>
+                <div>
 				${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
-				<h3>${_('Compare View')}</h3>
-				 ##&raquo; <span>${h.link_to(_('raw diff'),
-				##h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show'))}</span>
-				 ##&raquo; <span>${h.link_to(_('download diff'),
-				##h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download'))}</span>
 				</div>
 			</div>
 		</div>
 	    <div id="changeset_compare_view_content">
 			<div class="container">
-			<table class="compare_view_commits">
+			<table class="compare_view_commits noborder">
             %for cs in c.cs_ranges:
                 <tr>
                 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),14)}"/></div></td>
                 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td>
                 <td><div class="author">${h.person(cs.author)}</div></td>
                 <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
-                <td><div class="message">${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div></td>
+                <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td>
                 </tr>
             %endfor
             </table>
@@ -54,44 +51,39 @@
 	               %for cs in c.cs_ranges:
 	                   <div class="cur_cs">r${cs}</div>
 	                %for change,filenode,diff,cs1,cs2,st in c.changes[cs.raw_id]:
-	                    <div class="cs_${change}">${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.repo_name_slug('C%s-%s' % (cs.short_id,h.safe_unicode(filenode.path)))))}</div>
+	                    <div class="cs_${change}">${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID(cs.raw_id,filenode.path)))}</div>
 	                %endfor
-	               %endfor 
-	        </div>         
+	               %endfor
+	        </div>
 	    </div>
-	    
+
     </div>
-   %for cs in c.cs_ranges:    	
-	%for change,filenode,diff,cs1,cs2,st in c.changes[cs.raw_id]:
-		%if change !='removed':
-		<div style="clear:both;height:10px"></div>
-		<div class="diffblock">
-			<div id="${h.repo_name_slug('C%s-%s' % (cs.short_id,h.safe_unicode(filenode.path)))}" class="code-header">
-				<div class="changeset_header">
-					<span class="changeset_file">
-						${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
-						revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
-					</span>
-					%if 1:
-					&raquo; <span>${h.link_to(_('diff'),
-					h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff'))}</span>
-					&raquo; <span>${h.link_to(_('raw diff'),
-					h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw'))}</span>
-					&raquo; <span>${h.link_to(_('download diff'),
-					h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download'))}</span>
-					%endif
-				</div>
-			</div>
-			<div class="code-body">        
-					%if diff:
-						${diff|n}
-					%else:
-						${_('No changes in this file')}
-					%endif
-			</div>
-		</div>
-		%endif
-	%endfor
-   %endfor  
+    <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
+    <%namespace name="diff_block" file="/changeset/diff_block.html"/>
+     %for cs in c.cs_ranges:
+          ##${comment.comment_inline_form(cs)}
+          ## diff block
+          <h3 style="border:none;padding-top:8px;">${'r%s:%s' % (cs.revision,h.short_id(cs.raw_id))}</h3>
+          ${diff_block.diff_block(c.changes[cs.raw_id])}
+          ##${comment.comments(cs)}
+
+     %endfor
+     <script type="text/javascript">
+
+      YUE.onDOMReady(function(){
+
+          YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
+              var act = e.currentTarget.nextElementSibling;
+
+              if(YUD.hasClass(act,'active')){
+                  YUD.removeClass(act,'active');
+                  YUD.setStyle(act,'display','none');
+              }else{
+                  YUD.addClass(act,'active');
+                  YUD.setStyle(act,'display','');
+              }
+          });
+      })
+    </script>
     </div>
-</%def>
\ No newline at end of file
+</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/changeset/diff_block.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,41 @@
+## -*- coding: utf-8 -*-
+##usage:
+## <%namespace name="diff_block" file="/changeset/diff_block.html"/>
+## ${diff_block.diff_block(changes)}
+##
+<%def name="diff_block(changes)">
+
+%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)}" class="diffblock  margined comm">
+        <div class="code-header">
+            <div class="changeset_header">
+                <div class="changeset_file">
+                    ${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
+                    revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
+                </div>
+                <div class="diff-actions">
+                  <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))}
+                </div>
+                <span style="float:right;margin-top:-3px">
+                  <label>
+                  ${_('show inline comments')}
+                  ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(filenode.changeset.raw_id,filenode.path))}
+                  </label>
+                </span>
+            </div>
+        </div>
+        <div class="code-body">
+            <div class="full_f_path" path="${h.safe_unicode(filenode.path)}"></div>
+            ${diff|n}
+        </div>
+    </div>
+    %endif
+%endfor
+
+</%def>
--- a/rhodecode/templates/changeset/raw_changeset.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/changeset/raw_changeset.html	Sun Feb 26 17:25:09 2012 +0200
@@ -5,4 +5,4 @@
 ${c.parent_tmpl}
 ${c.changeset.message}
 
-${c.diffs|n}
\ No newline at end of file
+${c.diffs|n}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/email_templates/changeset_comment.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,6 @@
+## -*- coding: utf-8 -*-
+<%inherit file="main.html"/>
+
+<h4>${subject}</h4>
+
+${body}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/email_templates/default.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,4 @@
+## -*- coding: utf-8 -*-
+<%inherit file="main.html"/>
+
+${body}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/email_templates/main.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,9 @@
+${self.body()}
+
+
+<div>
+--
+<br/>
+<br/>
+<b>${_('This is an notification from RhodeCode.')}</b>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/email_templates/password_reset.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,12 @@
+## -*- coding: utf-8 -*-
+<%inherit file="main.html"/>
+
+Hello ${user}
+
+We received a request to create a new password for your account.
+
+You can generate it by clicking following URL:
+
+${reset_url}
+
+If you didn't request new password please ignore this email.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/email_templates/registration.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,9 @@
+## -*- coding: utf-8 -*-
+<%inherit file="main.html"/>
+
+A new user have registered in RhodeCode
+
+${body}
+
+
+View this user here: ${registered_user_url}
--- a/rhodecode/templates/errors/error_document.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/errors/error_document.html	Sun Feb 26 17:25:09 2012 +0200
@@ -6,10 +6,10 @@
         <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
 	    %if c.redirect_time:
 	        <meta http-equiv="refresh" content="${c.redirect_time}; url=${c.url_redirect}"/>
-	    %endif        
+	    %endif
         <link rel="icon" href="${h.url("/images/hgicon.png")}" type="image/png" />
         <meta name="robots" content="index, nofollow"/>
-            
+
         <!-- stylesheets -->
         <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
 	    <style type="text/css">
@@ -28,27 +28,26 @@
 	        margin:10px;
 	     }
 	    </style>
-    
+
     </head>
     <body>
 
         <div id="login">
-            <div class="table">            
+            <div class="table">
 				<div id="main_div">
 				    <div style="font-size:2.0em;margin: 10px">${c.rhodecode_name}</div>
 					<h1 class="error_message">${c.error_message}</h1>
-					
+
 					<p>${c.error_explanation}</p>
-					
+
 			        %if c.redirect_time:
 			            <p>${_('You will be redirected to %s in %s seconds') % (c.redirect_module,c.redirect_time)}</p>
-				    %endif		
-					
+				    %endif
+
 				</div>
             </div>
             <!-- end login -->
         </div>
     </body>
-    
+
 </html>
-
--- a/rhodecode/templates/files/file_diff.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/files/file_diff.html	Sun Feb 26 17:25:09 2012 +0200
@@ -13,7 +13,7 @@
 </%def>
 
 <%def name="page_nav()">
-    ${self.menu('files')}     
+    ${self.menu('files')}
 </%def>
 <%def name="main()">
 <div class="box">
@@ -21,33 +21,27 @@
     <div class="title">
         ${self.breadcrumbs()}
     </div>
-    <div class="table">
-		<div id="body" class="diffblock">
-			<div class="code-header">
-                <div class="changeset_header">
-                <span class="changeset_file">${h.link_to(c.f_path,h.url('files_home',repo_name=c.repo_name,
-				revision=c.changeset_2.raw_id,f_path=c.f_path))}</span>
-				 &raquo; <span>${h.link_to(_('diff'),
-				h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='diff'))}</span>
-				 &raquo; <span>${h.link_to(_('raw diff'),
-				h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='raw'))}</span>
-				 &raquo; <span>${h.link_to(_('download diff'),
-				h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='download'))}</span>
-				</div>
-			</div>
-			<div class="code-body">
-		 			%if c.no_changes:
-		            	${_('No changes')}
-		            %elif c.big_diff:
-		                ${_('Diff is to big to display')} ${h.link_to(_('raw diff'),
-                           h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='raw'))}
-		            %else:        
-						${c.cur_diff|n}
-		            %endif
-			</div>
-		</div>   
+    <div>
+    ## diff block
+    <%namespace name="diff_block" file="/changeset/diff_block.html"/>
+    ${diff_block.diff_block(c.changes)}
     </div>
-</div>    
-</%def>  
+</div>
+<script>
+YUE.onDOMReady(function(){
+
+    YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
+        var act = e.currentTarget.nextElementSibling;
 
-   
\ No newline at end of file
+        if(YUD.hasClass(act,'active')){
+            YUD.removeClass(act,'active');
+            YUD.setStyle(act,'display','none');
+        }else{
+            YUD.addClass(act,'active');
+            YUD.setStyle(act,'display','');
+        }
+    });
+
+})
+</script>
+</%def>
--- a/rhodecode/templates/files/files.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/files/files.html	Sun Feb 26 17:25:09 2012 +0200
@@ -10,13 +10,13 @@
     ${h.link_to(c.repo_name,h.url('files_home',repo_name=c.repo_name))}
     &raquo;
     ${_('files')}
-    %if c.files_list:
+    %if c.file:
         @ r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
-    %endif        
+    %endif
 </%def>
 
 <%def name="page_nav()">
-    ${self.menu('files')}     
+    ${self.menu('files')}
 </%def>
 
 <%def name="main()">
@@ -27,29 +27,22 @@
 	    <ul class="links">
 		    <li>
 		      <span style="text-transform: uppercase;"><a href="#">${_('branch')}: ${c.changeset.branch}</a></span>
-	        </li>          
-	    </ul>             
+	        </li>
+	    </ul>
     </div>
     <div class="table">
 		<div id="files_data">
-			%if c.files_list:
-				<h3 class="files_location">
-				    ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.files_list.path)}
-				</h3>
-					%if c.files_list.is_dir():
-						<%include file='files_browser.html'/>
-					%else:
-						<%include file='files_source.html'/>			
-					%endif	
-			%else:
-				<h2>
-					<a href="#" onClick="javascript:parent.history.back();" target="main">${_('Go back')}</a> 
-					${_('No files at given path')}: "${c.f_path or "/"}" 
-				</h2>
-			%endif
-		
-		</div>    
+			<%include file='files_ypjax.html'/>
+		</div>
     </div>
-</div>    
-	
-</%def>    
\ No newline at end of file
+</div>
+<script type="text/javascript">
+var YPJAX_TITLE = "${c.repo_name} ${_('Files')} - ${c.rhodecode_name}";
+var current_url = "${h.url.current()}";
+var node_list_url = '${h.url("files_home",repo_name=c.repo_name,revision=c.changeset.raw_id,f_path='__FPATH__')}';
+var url_base = '${h.url("files_nodelist_home",repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.file.path)}';
+var truncated_lbl = "${_('search truncated')}";
+var nomatch_lbl = "${_('no matching files')}";
+fileBrowserListeners(current_url, node_list_url, url_base, truncated_lbl, nomatch_lbl);
+</script>
+</%def>
--- a/rhodecode/templates/files/files_add.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/files/files_add.html	Sun Feb 26 17:25:09 2012 +0200
@@ -20,7 +20,7 @@
 </%def>
 
 <%def name="page_nav()">
-		${self.menu('files')}     
+		${self.menu('files')}
 </%def>
 <%def name="main()">
 <div class="box">
@@ -31,8 +31,8 @@
             <li>
               <span style="text-transform: uppercase;">
               <a href="#">${_('branch')}: ${c.cs.branch}</a></span>
-            </li>          
-        </ul>          
+            </li>
+        </ul>
     </div>
     <div class="table">
 		<div id="files_data">
@@ -46,16 +46,16 @@
                       </div>
                       <div class="input">
                           <input type="text" value="" size="30" name="filename" id="filename">
-                          <input type="button" class="ui-button-small" value="upload file" id="upload_file_enable">
+                          ${_('or')} <span class="ui-btn" id="upload_file_enable">${_('Upload file')}</span>
                       </div>
-                  </div>                    
+                  </div>
                   <div id="upload_file_container" class="field" style="display:none">
                     <div class="label">
                         <label for="location">${_('Upload file')}</label>
                     </div>
                     <div class="file">
                         <input type="file"  size="30" name="upload_file" id="upload_file">
-                        <input type="button" class="ui-button-small" value="create file" id="file_enable">                        
+                        ${_('or')} <span class="ui-btn" id="file_enable">${_('Create new file')}</span>
                     </div>
                   </div>
                    <div class="field">
@@ -66,45 +66,27 @@
                           <input type="text" value="${c.f_path}" size="30" name="location" id="location">
                           ${_('use / to separate directories')}
                       </div>
-                   </div>                                                                    
+                   </div>
               </div>
-            </div>            
+            </div>
 			<div id="body" class="codeblock">
-			    <div id="editor_container">    
+			    <div id="editor_container">
                     <pre id="editor_pre"></pre>
 				    <textarea id="editor" name="content" style="display:none"></textarea>
                 </div>
 				<div style="padding: 10px;color:#666666">${_('commit message')}</div>
 				<textarea id="commit" name="message" style="height: 100px;width: 99%;margin-left:4px"></textarea>
 			</div>
-			<div style="text-align: right;padding-top: 5px">
-			<input id="reset" type="button" value="${_('Reset')}" class="ui-button-small" />
-			${h.submit('commit',_('Commit changes'),class_="ui-button-small-blue")}
+			<div style="text-align: l;padding-top: 5px">
+            ${h.submit('commit',_('Commit changes'),class_="ui-btn")}
+            ${h.reset('reset',_('Reset'),class_="ui-btn")}
 			</div>
 			${h.end_form()}
 			<script type="text/javascript">
-			 var myCodeMirror = CodeMirror.fromTextArea(YUD.get('editor'),{
-	                mode:  "null",
-	                lineNumbers:true
-	              });
-			 YUE.on('reset','click',function(e){
-				 window.location="${h.url('files_home',repo_name=c.repo_name,revision=c.cs.revision,f_path=c.f_path)}";
-			 });
-             
-			 YUE.on('file_enable','click',function(){
-                 YUD.setStyle('editor_container','display','');
-                 YUD.setStyle('upload_file_container','display','none');
-                 YUD.setStyle('filename_container','display','');
-             });
-             
-			 YUE.on('upload_file_enable','click',function(){
-				 YUD.setStyle('editor_container','display','none');
-				 YUD.setStyle('upload_file_container','display','');
-				 YUD.setStyle('filename_container','display','none');
-			 });
-			 
+			var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path)}";
+		    initCodeMirror('editor',reset_url);
 			</script>
-		</div>    
+		</div>
     </div>
-</div>    
-</%def>   
\ No newline at end of file
+</div>
+</%def>
--- a/rhodecode/templates/files/files_annotate.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/files/files_annotate.html	Sun Feb 26 17:25:09 2012 +0200
@@ -13,7 +13,7 @@
 </%def>
 
 <%def name="page_nav()">
-		${self.menu('files')}     
+		${self.menu('files')}
 </%def>
 <%def name="main()">
 <div class="box">
@@ -23,55 +23,55 @@
         <ul class="links">
             <li>
               <span style="text-transform: uppercase;"><a href="#">${_('branch')}: ${c.cs.branch}</a></span>
-            </li>          
-        </ul>          
+            </li>
+        </ul>
     </div>
     <div class="table">
 		<div id="files_data">
 			<h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
-			<dl class="overview">
-				<dt>${_('Revision')}</dt>
-				<dd>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),
-						h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))} </dd>
-				<dt>${_('Size')}</dt>
-				<dd>${h.format_byte_size(c.file.size,binary=True)}</dd>
-    			<dt>${_('Mimetype')}</dt>
-				<dd>${c.file.mimetype}</dd>				
-				<dt>${_('Options')}</dt>
-				<dd>${h.link_to(_('show source'),
-						h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}  
-					/ ${h.link_to(_('show as raw'),
-						h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
-					/ ${h.link_to(_('download as raw'),
-						h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
-                    % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
-                     % if not c.file.is_binary:                   
-                    / ${h.link_to(_('edit'),
-                        h.url('files_edit_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
-                     % endif 
-                    % endif						
-				</dd>
-			    <dt>${_('History')}</dt>
+			<dl>
+			    <dt style="padding-top:10px;font-size:16px">${_('History')}</dt>
 			    <dd>
 			        <div>
 			        ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
 			        ${h.hidden('diff2',c.file.last_changeset.raw_id)}
 			        ${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)}
-			        ${h.submit('diff','diff to revision',class_="ui-button-small")}
-			        ${h.submit('show_rev','show at revision',class_="ui-button-small")}
+			        ${h.submit('diff','diff to revision',class_="ui-btn")}
+			        ${h.submit('show_rev','show at revision',class_="ui-btn")}
 			        ${h.end_form()}
 			        </div>
-			    </dd>					
+			    </dd>
 			</dl>
 			<div id="body" class="codeblock">
-				<div class="code-header">
-					<div class="revision">${c.file.name}@r${c.file.last_changeset.revision}:${h.short_id(c.file.last_changeset.raw_id)}</div>
-					<div class="commit">"${c.file.message}"</div>
-				</div>
+                <div class="code-header">
+                    <div class="stats">
+                        <div class="left"><img src="${h.url('/images/icons/file.png')}"/></div>
+                        <div class="left item">${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))}</div>
+                        <div class="left item">${h.format_byte_size(c.file.size,binary=True)}</div>
+                        <div class="left item last">${c.file.mimetype}</div>
+                        <div class="buttons">
+                          ${h.link_to(_('show source'),h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
+                          ${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
+                          ${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
+                          % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
+                           % if not c.file.is_binary:
+                            ${h.link_to(_('edit'),h.url('files_edit_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
+                           % endif
+                          % endif
+                        </div>
+                    </div>
+                    <div class="author">
+                        <div class="gravatar">
+                            <img alt="gravatar" src="${h.gravatar_url(h.email(c.cs.author),16)}"/>
+                        </div>
+                        <div title="${c.cs.author}" class="user">${h.person(c.cs.author)}</div>
+                    </div>
+                    <div class="commit">${c.file.last_changeset.message}</div>
+                </div>
 				<div class="code-body">
 			       %if c.file.is_binary:
 			           ${_('Binary file (%s)') % c.file.mimetype}
-			       %else:				
+			       %else:
 					% if c.file.size < c.cut_off_limit:
 						${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
 					%else:
@@ -81,13 +81,13 @@
 			       <script type="text/javascript">
 			           function highlight_lines(lines){
 			               for(pos in lines){
-			                 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');                       
+			                 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
 			               }
-			           }       
+			           }
 			           page_highlights = location.href.substring(location.href.indexOf('#')+1).split('L');
 			           if (page_highlights.length == 2){
 			              highlight_ranges  = page_highlights[1].split(",");
-			
+
 			              var h_lines = [];
 			              for (pos in highlight_ranges){
 			                   var _range = highlight_ranges[pos].split('-');
@@ -98,26 +98,26 @@
 			                           for(var i=start;i<=end;i++){
 			                               h_lines.push(i);
 			                           }
-			                       } 
+			                       }
 			                   }
 			                   else{
 			                       h_lines.push(parseInt(highlight_ranges[pos]));
 			                   }
 			             }
 			           highlight_lines(h_lines);
-			           
-			           //remember original location 
+
+			           //remember original location
 			           var old_hash  = location.href.substring(location.href.indexOf('#'));
-			           
-			           // this makes a jump to anchor moved by 3 posstions for padding 
+
+			           // this makes a jump to anchor moved by 3 posstions for padding
 			           window.location.hash = '#L'+Math.max(parseInt(h_lines[0])-3,1);
-			           
-			           //sets old anchor 
+
+			           //sets old anchor
 			           window.location.hash = old_hash;
-			           
+
 			           }
-			       </script>					
-				   %endif				
+			       </script>
+				   %endif
 				</div>
 			</div>
             <script type="text/javascript">
@@ -129,8 +129,8 @@
                     window.location = url;
                     });
                });
-            </script>			
-		</div>    
+            </script>
+		</div>
     </div>
-</div>    
-</%def>   
+</div>
+</%def>
--- a/rhodecode/templates/files/files_browser.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/files/files_browser.html	Sun Feb 26 17:25:09 2012 +0200
@@ -10,12 +10,12 @@
 		<div class="browser-nav">
 			${h.form(h.url.current())}
 			<div class="info_box">
-	          <span class="rev">${_('view')}@rev</span> 
-	          <a class="ui-button-small" href="${c.url_prev}" title="${_('previous revision')}">&laquo;</a>
+	          <span class="rev">${_('view')}@rev</span>
+	          <a class="ui-btn" href="${c.url_prev}" title="${_('previous revision')}">&laquo;</a>
 	          ${h.text('at_rev',value=c.changeset.revision,size=5)}
-	          <a class="ui-button-small" href="${c.url_next}" title="${_('next revision')}">&raquo;</a>
-	          ## ${h.submit('view',_('view'),class_="ui-button-small")}
-		    </div>           
+	          <a class="ui-btn" href="${c.url_next}" title="${_('next revision')}">&raquo;</a>
+	          ## ${h.submit('view',_('view'),class_="ui-btn")}
+		    </div>
 			${h.end_form()}
 		</div>
 	    <div class="browser-branch">
@@ -24,136 +24,22 @@
 	    </div>
         <div class="browser-search">
               <div id="search_activate_id" class="search_activate">
-                  <a class="ui-button-small" id="filter_activate" href="#">${_('search file list')}</a>
+                  <a class="ui-btn" id="filter_activate" href="#">${_('search file list')}</a>
               </div>
-              % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):            
+              % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
                     <div id="add_node_id" class="add_node">
-                        <a class="ui-button-small" href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('add new file')}</a>
+                        <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('add new file')}</a>
                     </div>
-              % endif               
+              % endif
         <div>
             <div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
             <div id="node_filter_box" style="display:none">
-            ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.files_list.path)}/<input type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
-            
-            <script type="text/javascript">
-            
-            YUE.on('stay_at_branch','click',function(e){
-                if(e.target.checked){
-                    var uri = "${h.url.current(branch='__BRANCH__')}"
-                    uri = uri.replace('__BRANCH__',e.target.value);
-                    window.location = uri;
-                }
-                else{
-                    window.location = "${h.url.current()}";
-                }
-                
-            })            
-            
-            var n_filter = YUD.get('node_filter');
-            var F = YAHOO.namespace('node_filter');
-            
-            var url = '${h.url("files_nodelist_home",repo_name="__REPO__",revision="__REVISION__",f_path="__FPATH__")}';
-            var node_url = '${h.url("files_home",repo_name="__REPO__",revision="__REVISION__",f_path="__FPATH__")}';
-            
-            url  = url.replace('__REPO__','${c.repo_name}');
-            url  = url.replace('__REVISION__','${c.changeset.raw_id}');
-            url  = url.replace('__FPATH__','${c.files_list.path}');
+            ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}/<input class="init" type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
+            </div>
+        </div>
+        </div>
+    </div>
 
-            node_url  = node_url.replace('__REPO__','${c.repo_name}');
-            node_url  = node_url.replace('__REVISION__','${c.changeset.raw_id}');
-            
-            
-            F.filterTimeout = null;
-            var nodes = null;
-            
-            
-            F.initFilter = function(){
-              YUD.setStyle('node_filter_box_loading','display','');
-              YUD.setStyle('search_activate_id','display','none');
-              YUD.setStyle('add_node_id','display','none');
-              YUC.initHeader('X-PARTIAL-XHR',true);
-              YUC.asyncRequest('GET',url,{
-                  success:function(o){
-                  	nodes = JSON.parse(o.responseText);
-                  	YUD.setStyle('node_filter_box_loading','display','none');
-                  	YUD.setStyle('node_filter_box','display','');
-                  },
-                  failure:function(o){
-                      console.log('failed to load');
-                  }
-              },null);            
-            }
-            
-            F.updateFilter  = function(e) {
-            	
-            	return function(){
-                    // Reset timeout 
-                    F.filterTimeout = null;
-                    var query = e.target.value;
-                    var match = [];
-                    var matches = 0;
-                    var matches_max = 20;
-                    if (query != ""){
-                        for(var i=0;i<nodes.length;i++){
-                            var pos = nodes[i].toLowerCase().indexOf(query)
-                            if(query && pos != -1){
-                                
-                                matches++
-                                //show only certain amount to not kill browser 
-                                if (matches > matches_max){
-                                    break;
-                                }
-                                
-                                var n = nodes[i];
-                                var n_hl = n.substring(0,pos)
-                                  +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
-                                  +n.substring(pos+query.length)                    
-                                match.push('<tr><td><a class="browser-file" href="{0}">{1}</a></td><td colspan="5"></td></tr>'.format(node_url.replace('__FPATH__',n),n_hl));
-                            }
-                            if(match.length >= matches_max){
-                                match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format("${_('search truncated')}"));
-                            }
-                            
-                        }                    	
-                    }
-                    
-                    if(query != ""){
-                        YUD.setStyle('tbody','display','none');
-                        YUD.setStyle('tbody_filtered','display','');
-                        
-                        if (match.length==0){
-                          match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format("${_('no matching files')}"));
-                        }                        	
-                        
-                    	YUD.get('tbody_filtered').innerHTML = match.join("");	
-                    }
-                    else{
-                    	YUD.setStyle('tbody','display','');
-                    	YUD.setStyle('tbody_filtered','display','none');
-                    }
-                    
-            	}
-            }
-            
-            
-            YUE.on(YUD.get('filter_activate'),'click',function(){
-                F.initFilter();
-            })
-            YUE.on(n_filter,'click',function(){
-                n_filter.value = '';
-             });
-            YUE.on(n_filter,'keyup',function(e){
-                clearTimeout(F.filterTimeout); 
-                F.filterTimeout = setTimeout(F.updateFilter(e),600);
-            });            
-            </script>
-            
-            </div>        
-        </div>
-        </div>      
-    </div>
-    
 	<div class="browser-body">
 		<table class="code-browser">
 		         <thead>
@@ -166,12 +52,12 @@
 		                 <th>${_('Last commiter')}</th>
 		             </tr>
 		         </thead>
-                
+
                 <tbody id="tbody">
-          		%if c.files_list.parent:
+          		%if c.file.parent:
          		<tr class="parity0">
-	          		<td>		          		
-	          			${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.files_list.parent.path),class_="browser-dir")}
+	          		<td>
+	          			${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.file.parent.path),class_="browser-dir ypjax-link")}
 	          		</td>
 	          		<td></td>
 	          		<td></td>
@@ -180,16 +66,16 @@
 	          		<td></td>
 				</tr>
           		%endif
-		         	
-		    %for cnt,node in enumerate(c.files_list):
+
+		    %for cnt,node in enumerate(c.file):
 				<tr class="parity${cnt%2}">
 		             <td>
-						${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=h.safe_unicode(node.path)),class_=file_class(node))}
+						${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=h.safe_unicode(node.path)),class_=file_class(node)+" ypjax-link")}
 		             </td>
 		             <td>
 		             %if node.is_file():
 		             	${h.format_byte_size(node.size,binary=True)}
-		             %endif	
+		             %endif
 		             </td>
 		             <td>
 		              %if node.is_file():
@@ -198,8 +84,9 @@
 		             </td>
 		             <td>
 		             	%if node.is_file():
-		             		<span class="tooltip" title="${node.last_changeset.raw_id}">
-		             		${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}</span>
+		             		<div class="tooltip" title="${node.last_changeset.message}">
+		             		<pre>${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}</pre>
+                            </div>
 		             	%endif
 		             </td>
 		             <td>
@@ -210,14 +97,16 @@
 		             </td>
 		             <td>
 		             	%if node.is_file():
-		             		${node.last_changeset.author}
-		             	%endif                    
+		             		<span title="${node.last_changeset.author}">
+                            ${h.person(node.last_changeset.author)}
+                            </span>
+		             	%endif
 		             </td>
 				</tr>
 			%endfor
                 </tbody>
                 <tbody id="tbody_filtered" style="display:none">
-                </tbody>                
+                </tbody>
 		</table>
 	</div>
-</div>
\ No newline at end of file
+</div>
--- a/rhodecode/templates/files/files_edit.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/files/files_edit.html	Sun Feb 26 17:25:09 2012 +0200
@@ -20,7 +20,7 @@
 </%def>
 
 <%def name="page_nav()">
-		${self.menu('files')}     
+		${self.menu('files')}
 </%def>
 <%def name="main()">
 <div class="box">
@@ -31,34 +31,48 @@
             <li>
               <span style="text-transform: uppercase;">
               <a href="#">${_('branch')}: ${c.cs.branch}</a></span>
-            </li>          
-        </ul>          
+            </li>
+        </ul>
     </div>
     <div class="table">
 		<div id="files_data">
 			<h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
 			${h.form(h.url.current(),method='post',id='eform')}
 			<div id="body" class="codeblock">
+            <div class="code-header">
+                <div class="stats">
+                    <div class="left"><img src="${h.url('/images/icons/file.png')}"/></div>
+                    <div class="left item">${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))}</div>
+                    <div class="left item">${h.format_byte_size(c.file.size,binary=True)}</div>
+                    <div class="left item last">${c.file.mimetype}</div>
+                    <div class="buttons">
+                      ${h.link_to(_('show annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
+                      ${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
+                      ${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
+                      % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
+                       % if not c.file.is_binary:
+                        ${h.link_to(_('source'),h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
+                       % endif
+                      % endif
+                    </div>
+                </div>
+                <div class="commit">${_('Editing file')}: ${c.file.path}</div>
+            </div>
 			    <pre id="editor_pre"></pre>
-				<textarea id="editor" name="content" style="display:none">${c.file.content|n}</textarea>
+				<textarea id="editor" name="content" style="display:none">${h.escape(c.file.content)|n}</textarea>
 				<div style="padding: 10px;color:#666666">${_('commit message')}</div>
 				<textarea id="commit" name="message" style="height: 60px;width: 99%;margin-left:4px"></textarea>
 			</div>
-			<div style="text-align: right;padding-top: 5px">
-			<input id="reset" type="button" value="${_('Reset')}" class="ui-button-small" />
-			${h.submit('commit',_('Commit changes'),class_="ui-button-small-blue")}
+			<div style="text-align: left;padding-top: 5px">
+            ${h.submit('commit',_('Commit changes'),class_="ui-btn")}
+            ${h.reset('reset',_('Reset'),class_="ui-btn")}
 			</div>
 			${h.end_form()}
 			<script type="text/javascript">
-			 var myCodeMirror = CodeMirror.fromTextArea(YUD.get('editor'),{
-	                mode:  "null",
-	                lineNumbers:true
-	              });
-			 YUE.on('reset','click',function(){
-				 window.location="${h.url('files_home',repo_name=c.repo_name,revision=c.cs.revision,f_path=c.file.path)}";
-			 })
+			var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.file.path)}";
+			initCodeMirror('editor',reset_url);
 			</script>
-		</div>    
+		</div>
     </div>
-</div>    
-</%def>   
\ No newline at end of file
+</div>
+</%def>
--- a/rhodecode/templates/files/files_source.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/files/files_source.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,62 +1,59 @@
 <dl>
-	<dt>${_('Revision')}</dt>
-	<dd>
-		${h.link_to("r%s:%s" % (c.files_list.last_changeset.revision,h.short_id(c.files_list.last_changeset.raw_id)),
-						h.url('changeset_home',repo_name=c.repo_name,revision=c.files_list.last_changeset.raw_id))} 
-	</dd>
-	<dt>${_('Size')}</dt>
-	<dd>${h.format_byte_size(c.files_list.size,binary=True)}</dd>
-	<dt>${_('Mimetype')}</dt>
-	<dd>${c.files_list.mimetype}</dd>
-	<dt>${_('Options')}</dt>
-	<dd>${h.link_to(_('show annotation'),
-			h.url('files_annotate_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
-		 / ${h.link_to(_('show as raw'),
-			h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}			
-		 / ${h.link_to(_('download as raw'),
-			h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
-        % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):			
-         % if not c.files_list.is_binary:
-         / ${h.link_to(_('edit'),
-            h.url('files_edit_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
-         % endif
-        % endif			
-	</dd>
-	<dt>${_('History')}</dt>
+	<dt style="padding-top:10px;font-size:16px">${_('History')}</dt>
 	<dd>
 		<div>
 		${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
-		${h.hidden('diff2',c.files_list.last_changeset.raw_id)}
-		${h.select('diff1',c.files_list.last_changeset.raw_id,c.file_history)}
-		${h.submit('diff','diff to revision',class_="ui-button-small")}
-		${h.submit('show_rev','show at revision',class_="ui-button-small")}
+		${h.hidden('diff2',c.file.last_changeset.raw_id)}
+		${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)}
+		${h.submit('diff','diff to revision',class_="ui-btn")}
+		${h.submit('show_rev','show at revision',class_="ui-btn")}
 		${h.end_form()}
 		</div>
 	</dd>
-</dl>	
+</dl>
 
-	
 <div id="body" class="codeblock">
 	<div class="code-header">
-		<div class="revision">${c.files_list.name}@r${c.files_list.last_changeset.revision}:${h.short_id(c.files_list.last_changeset.raw_id)}</div>
-		<div class="commit">"${c.files_list.last_changeset.message}"</div>
+        <div class="stats">
+            <div class="left img"><img src="${h.url('/images/icons/file.png')}"/></div>
+            <div class="left item"><pre>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))}</pre></div>
+            <div class="left item"><pre>${h.format_byte_size(c.file.size,binary=True)}</pre></div>
+            <div class="left item last"><pre>${c.file.mimetype}</pre></div>
+            <div class="buttons">
+              ${h.link_to(_('show annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
+              ${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
+              ${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
+              % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
+               % if not c.file.is_binary:
+                ${h.link_to(_('edit'),h.url('files_edit_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
+               % endif
+              % endif
+            </div>
+        </div>
+        <div class="author">
+            <div class="gravatar">
+                <img alt="gravatar" src="${h.gravatar_url(h.email(c.file.last_changeset.author),16)}"/>
+            </div>
+            <div title="${c.file.last_changeset.author}" class="user">${h.person(c.file.last_changeset.author)}</div>
+        </div>
+		<div class="commit">${h.urlify_commit(c.file.last_changeset.message,c.repo_name)}</div>
 	</div>
 	<div class="code-body">
-	   %if c.files_list.is_binary:
-	       ${_('Binary file (%s)') % c.files_list.mimetype}
+	   %if c.file.is_binary:
+	       ${_('Binary file (%s)') % c.file.mimetype}
 	   %else:
-		% if c.files_list.size < c.cut_off_limit:
-			${h.pygmentize(c.files_list,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
+		% if c.file.size < c.cut_off_limit:
+			${h.pygmentize(c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
 		%else:
 			${_('File is too big to display')} ${h.link_to(_('show as raw'),
-			h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
+			h.url('files_raw_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path))}
 		%endif
        <script type="text/javascript">
            function highlight_lines(lines){
                for(pos in lines){
-                 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');                       
+                 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
                }
-           }       
+           }
            page_highlights = location.href.substring(location.href.indexOf('#')+1).split('L');
            if (page_highlights.length == 2){
               highlight_ranges  = page_highlights[1].split(",");
@@ -71,26 +68,26 @@
                            for(var i=start;i<=end;i++){
                                h_lines.push(i);
                            }
-                       } 
+                       }
                    }
                    else{
                        h_lines.push(parseInt(highlight_ranges[pos]));
                    }
              }
            highlight_lines(h_lines);
-           
-           //remember original location 
+
+           //remember original location
            var old_hash  = location.href.substring(location.href.indexOf('#'));
-           
-           // this makes a jump to anchor moved by 3 posstions for padding 
+
+           // this makes a jump to anchor moved by 3 posstions for padding
            window.location.hash = '#L'+Math.max(parseInt(h_lines[0])-3,1);
-           
-           //sets old anchor 
+
+           //sets old anchor
            window.location.hash = old_hash;
-           
+
            }
        </script>
-     %endif		
+     %endif
 	</div>
 </div>
 
@@ -98,77 +95,10 @@
 YUE.onDOMReady(function(){
     YUE.on('show_rev','click',function(e){
     	YUE.preventDefault(e);
-        var cs = YAHOO.util.Dom.get('diff1').value;
+        var cs = YUD.get('diff1').value;
         var url = "${h.url('files_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
         window.location = url;
     });
-    
-    function getIdentNode(n){
-    	//iterate thru nodes untill matched interesting node !
-    	
-    	if (typeof n == 'undefined'){
-    		return -1
-    	}
-    	
-    	if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
-   			return n
-   		}
-    	else{
-    		return getIdentNode(n.parentNode);
-    	}
-    }
-    
-    function getSelectionLink() {
-        //get selection from start/to nodes    	
-        if (typeof window.getSelection != "undefined") {
-        	s = window.getSelection();
-
-           	from = getIdentNode(s.anchorNode);
-           	till = getIdentNode(s.focusNode);
-            
-            f_int = parseInt(from.id.replace('L',''));
-            t_int = parseInt(till.id.replace('L',''));
-            
-            if (f_int > t_int){
-            	//highlight from bottom 
-            	offset = -35;
-            	ranges = [t_int,f_int];
-            	
-            }
-            else{
-            	//highligth from top 
-            	offset = 35;
-            	ranges = [f_int,t_int];
-            }
-            
-            if (ranges[0] != ranges[1]){
-	            if(YUD.get('linktt') == null){
-	                hl_div = document.createElement('div');
-	                hl_div.id = 'linktt';
-	            }
-	            anchor = '#L'+ranges[0]+'-'+ranges[1];
-	            hl_div.innerHTML = '';
-	            l = document.createElement('a');
-	            l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
-	            l.innerHTML = "${_('Selection link')}"
-	            hl_div.appendChild(l);
-	            
-	            YUD.get('body').appendChild(hl_div);
-	            
-	            xy = YUD.getXY(till.id);
-	            
-	            YUD.addClass('linktt','yui-tt');
-	            YUD.setStyle('linktt','top',xy[1]+offset+'px');
-	            YUD.setStyle('linktt','left',xy[0]+'px');
-	            YUD.setStyle('linktt','visibility','visible');
-            }
-            else{
-            	YUD.setStyle('linktt','visibility','hidden');
-            }
-        }
-   }
-    
-    YUE.on('hlcode','mouseup',getSelectionLink)
-    
+    YUE.on('hlcode','mouseup',getSelectionLink("${_('Selection link')}"))
    });
 </script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/files/files_ypjax.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,15 @@
+%if c.file:
+    <h3 class="files_location">
+        ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}
+    </h3>
+        %if c.file.is_dir():
+            <%include file='files_browser.html'/>
+        %else:
+            <%include file='files_source.html'/>
+        %endif
+%else:
+    <h2>
+        <a href="#" onClick="javascript:parent.history.back();" target="main">${_('Go back')}</a>
+        ${_('No files at given path')}: "${c.f_path or "/"}"
+    </h2>
+%endif
--- a/rhodecode/templates/followers/followers.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/followers/followers.html	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
 <%def name="breadcrumbs_links()">
     ${h.link_to(u'Home',h.url('/'))}
-    &raquo; 
+    &raquo;
     ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
     &raquo;
     ${_('followers')}
@@ -26,7 +26,7 @@
     <div class="table">
         <div id="followers">
             ${c.followers_data}
-        </div>   
+        </div>
     </div>
-</div>    
-</%def> 
\ No newline at end of file
+</div>
+</%def>
--- a/rhodecode/templates/followers/followers_data.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/followers/followers_data.html	Sun Feb 26 17:25:09 2012 +0200
@@ -9,11 +9,11 @@
             <span style="font-size: 20px"> <b>${f.user.username}</b> (${f.user.name} ${f.user.lastname})</span>
         </div>
         <div style="clear:both;padding-top: 10px"></div>
-        <div class="follower_date">${_('Started following')} - 
+        <div class="follower_date">${_('Started following')} -
         <span class="tooltip" title="${f.follows_from}"> ${h.age(f.follows_from)}</span></div>
         <div style="border-bottom: 1px solid #DDD;margin:10px 0px 10px 0px"></div>
-    </div>                
-% endfor 
+    </div>
+% endfor
 
 <div class="pagination-wh pagination-left">
 <script type="text/javascript">
@@ -25,4 +25,4 @@
 });
 </script>
 ${c.followers_pager.pager('$link_previous ~2~ $link_next')}
-</div>
\ No newline at end of file
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/forks/fork.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,86 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${c.repo_name} ${_('Fork')} - ${c.rhodecode_name}
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${h.link_to(u'Home',h.url('/'))}
+    &raquo;
+    ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
+    &raquo;
+    ${_('fork')}
+</%def>
+
+<%def name="page_nav()">
+	${self.menu('')}
+</%def>
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+    ${h.form(url('repo_fork_create_home',repo_name=c.repo_info.repo_name))}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+            <div class="field">
+              <div class="label">
+                  <label for="repo_name">${_('Fork name')}:</label>
+              </div>
+              <div class="input">
+                  ${h.text('repo_name',class_="small")}
+                  ${h.hidden('repo_type',c.repo_info.repo_type)}
+                  ${h.hidden('fork_parent_id',c.repo_info.repo_id)}
+              </div>
+            </div>
+            <div class="field">
+                 <div class="label">
+                     <label for="repo_group">${_('Repository group')}:</label>
+                 </div>
+                 <div class="input">
+                     ${h.select('repo_group','',c.repo_groups,class_="medium")}
+                 </div>
+            </div>
+            <div class="field">
+                <div class="label label-textarea">
+                    <label for="description">${_('Description')}:</label>
+                </div>
+                <div class="textarea text-area editor">
+                    ${h.textarea('description',cols=23,rows=5)}
+                </div>
+             </div>
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="private">${_('Private')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('private',value="True")}
+                </div>
+            </div>
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="private">${_('Copy permissions')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('copy_permissions',value="True")}
+                </div>
+             </div>
+            <div class="field">
+                <div class="label label-checkbox">
+                    <label for="private">${_('Update after clone')}:</label>
+                </div>
+                <div class="checkboxes">
+                    ${h.checkbox('update_after_clone',value="True")}
+                </div>
+             </div>
+	        <div class="buttons">
+	          ${h.submit('',_('fork this repository'),class_="ui-button")}
+	        </div>
+        </div>
+    </div>
+    ${h.end_form()}
+</div>
+</%def>
--- a/rhodecode/templates/forks/forks.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/forks/forks.html	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
 <%def name="breadcrumbs_links()">
     ${h.link_to(u'Home',h.url('/'))}
-    &raquo; 
+    &raquo;
     ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
     &raquo;
     ${_('forks')}
@@ -26,7 +26,7 @@
     <div class="table">
         <div id="forks">
             ${c.forks_data}
-        </div>   
+        </div>
     </div>
-</div>    
-</%def> 
\ No newline at end of file
+</div>
+</%def>
--- a/rhodecode/templates/forks/forks_data.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/forks/forks_data.html	Sun Feb 26 17:25:09 2012 +0200
@@ -8,15 +8,15 @@
 	                <img alt="gravatar" src="${h.gravatar_url(f.user.email,24)}"/>
 	            </div>
 	            <span style="font-size: 20px">
-	             <b>${f.user.username}</b> (${f.user.name} ${f.user.lastname}) / 
+	             <b>${f.user.username}</b> (${f.user.name} ${f.user.lastname}) /
 	              ${h.link_to(f.repo_name,h.url('summary_home',repo_name=f.repo_name))}
 	             </span>
 	             <div style="padding:5px 3px 3px 42px;">${f.description}</div>
 	        </div>
 	        <div style="clear:both;padding-top: 10px"></div>
-	        <div class="follower_date">${_('forked')} - 
+	        <div class="follower_date">${_('forked')} -
 	        <span class="tooltip" title="${f.created_on}"> ${h.age(f.created_on)}</span></div>
-	        <div style="border-bottom: 1px solid #DDD;margin:10px 0px 10px 0px"></div>            
+	        <div style="border-bottom: 1px solid #DDD;margin:10px 0px 10px 0px"></div>
 	    </div>
 	% endfor
   <div class="pagination-wh pagination-left">
@@ -29,7 +29,7 @@
   });
   </script>
   ${c.forks_pager.pager('$link_previous ~2~ $link_next')}
-  </div>  
+  </div>
 % else:
-	${_('There are no forks yet')} 
-% endif
\ No newline at end of file
+	${_('There are no forks yet')}
+% endif
--- a/rhodecode/templates/index.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/index.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,14 +1,8 @@
 ## -*- coding: utf-8 -*-
 <%inherit file="base/base.html"/>
-<%def name="title()">
-    ${_('Dashboard')} - ${c.rhodecode_name}
-</%def>
-<%def name="breadcrumbs()">
-	${c.rhodecode_name}
-</%def>
-<%def name="page_nav()">
-	${self.menu('home')}
-</%def>
+<%def name="title()">${_('Dashboard')} - ${c.rhodecode_name}</%def>
+<%def name="breadcrumbs()"></%def>
+<%def name="page_nav()">${self.menu('home')}</%def>
 <%def name="main()">
     	<%include file="index_base.html" args="parent=self"/>
-</%def>    
+</%def>
--- a/rhodecode/templates/index_base.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/index_base.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,54 +1,60 @@
-<%page args="parent" /> 
+<%page args="parent" />
     <div class="box">
         <!-- box / title -->
         <div class="title">
             <h5>
-            <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
-            ${parent.breadcrumbs()} <span id="repo_count"></span> ${_('repositories')} 
+            <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
             </h5>
             %if c.rhodecode_user.username != 'default':
                 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
                 <ul class="links">
                   <li>
-                    <span>${h.link_to(_('ADD NEW REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
-                  </li>          
-                </ul>           
+                    <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
+                  </li>
+                </ul>
                 %endif
             %endif
         </div>
         <!-- end box / title -->
         <div class="table">
            % if c.groups:
-            <table>
-                <thead>
+            <div id='groups_list_wrap' class="yui-skin-sam">
+              <table id="groups_list">
+                  <thead>
+                      <tr>
+                          <th class="left"><a href="#">${_('Group name')}</a></th>
+                          <th class="left"><a href="#">${_('Description')}</a></th>
+                          ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
+                      </tr>
+                  </thead>
+
+                  ## REPO GROUPS
+                  % for gr in c.groups:
                     <tr>
-                        <th class="left"><a href="#">${_('Group name')}</a></th>
-                        <th class="left"><a href="#">${_('Description')}</a></th>
-                        ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
+                        <td>
+                            <div style="white-space: nowrap">
+                            <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
+                            ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
+                            </div>
+                        </td>
+                        <td>${gr.group_description}</td>
+                        ## this is commented out since for multi nested repos can be HEAVY!
+                        ## in number of executed queries during traversing uncomment at will
+                        ##<td><b>${gr.repositories_recursive_count}</b></td>
                     </tr>
-                </thead>
-                
-                ## REPO GROUPS
-                
-                % for gr in c.groups:
-                  <tr>
-                      <td>
-                          <div style="white-space: nowrap">
-                          <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
-                          ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
-                          </div>
-                      </td>
-                      <td>${gr.group_description}</td>
-                      ##<td><b>${gr.repositories.count()}</b></td>
-                  </tr>
-                % endfor
-                
-            </table>
+                  % endfor
+
+              </table>
+            </div>
             <div style="height: 20px"></div>
             % endif
             <div id="welcome" style="display:none;text-align:center">
                 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
             </div>
+            <div id='repos_list_wrap' class="yui-skin-sam">
+            <%cnt=0%>
+            <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
+
             <table id="repos_list">
             <thead>
                 <tr>
@@ -63,87 +69,37 @@
                 </tr>
             </thead>
             <tbody>
-            %for cnt,repo in enumerate(c.repos_list):
+            %for cnt,repo in enumerate(c.repos_list,1):
                 <tr class="parity${cnt%2}">
+                    ##QUICK MENU
                     <td class="quick_repo_menu">
-                    <ul class="menu_items hidden">
-                      <li>
-                         <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo['name'])}">
-                         <span class="icon">
-                             <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
-                         </span>
-                         <span>${_('Summary')}</span>                 
-                         </a>             
-                      </li>
-                      <li>
-                         <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo['name'])}">
-                         <span class="icon">
-                             <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
-                         </span>
-                         <span>${_('Changelog')}</span>                 
-                         </a>             
-                      </li>
-                      <li>
-                         <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo['name'])}">
-                         <span class="icon">
-                             <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
-                         </span>
-                         <span>${_('Files')}</span>                 
-                         </a>             
-                      </li>                                    
-                    </ul>
+                      ${dt.quick_menu(repo['name'])}
                     </td>
-                    <td>
-                    ## TYPE OF REPO
-                    <div style="white-space: nowrap">
-                     %if repo['dbrepo']['repo_type'] =='hg':
-                       <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
-                     %elif repo['dbrepo']['repo_type'] =='git':
-                       <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
-                     %endif 
-                    
-                     ##PRIVATE/PUBLIC
-                     %if repo['dbrepo']['private']:
-                        <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
-                     %else:
-                        <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
-                     %endif
-                    
-                    ##NAME   
-                    ${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="${_('fork')}"
-                        title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}" 
-                        src="${h.url('/images/icons/arrow_divide.png')}"/></a>
-                    %endif
-                    </div>
+                    ##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>
                     ##DESCRIPTION
                     <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
                        ${h.truncate(repo['description'],60)}</span>
                     </td>
-                    ##LAST CHANGE
-                    <td>
-                      <span class="tooltip" title="${repo['last_change']}">
-                      ${h.age(repo['last_change'])}</span>
-                    </td>
+                    ##LAST CHANGE DATE
                     <td>
-                        %if repo['rev']>=0:
-                        <a title="${h.tooltip('%s\n%s' % (repo['author'],repo['last_msg']))}" class="tooltip" href="${h.url('changeset_home',repo_name=repo['name'],revision=repo['tip'])}">${'r%s:%s' % (repo['rev'],h.short_id(repo['tip']))}</a>
-                        %else:
-                            ${_('No changesets yet')}
-                        %endif    
+                      <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
                     </td>
+                    ##LAST REVISION
+                    <td>
+                        ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
+                    </td>
+                    ##
                     <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
                     <td>
                       %if c.rhodecode_user.username != 'default':
                         <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon"  href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
                       %else:
                         <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon"  href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
-                      %endif:                       
-                    </td>        
+                      %endif:
+                    </td>
                     <td>
                       %if c.rhodecode_user.username != 'default':
                         <a title="${_('Subscribe to %s atom feed')%repo['name']}"  class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
@@ -156,71 +112,86 @@
             </tbody>
             </table>
             </div>
+        </div>
     </div>
-    
-    
-    <script type="text/javascript">
-     var D = YAHOO.util.Dom;
-     var E = YAHOO.util.Event;
-     var S = YAHOO.util.Selector;
-     
-     var q_filter = D.get('q_filter');
-     var F = YAHOO.namespace('q_filter'); 
-     
-     E.on(q_filter,'click',function(){
-        q_filter.value = '';
-     });
+    <script>
+      YUD.get('repo_count').innerHTML = ${cnt};
+      var func = function(node){
+          return node.parentNode.parentNode.parentNode.parentNode;
+      }
+
+
+      // groups table sorting
+      var myColumnDefs = [
+          {key:"name",label:"${_('Group Name')}",sortable:true,
+              sortOptions: { sortFunction: groupNameSort }},
+          {key:"desc",label:"${_('Description')}",sortable:true},
+      ];
+
+      var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
+
+      myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
+      myDataSource.responseSchema = {
+          fields: [
+              {key:"name"},
+              {key:"desc"},
+          ]
+      };
+
+      var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
+              {
+               sortedBy:{key:"name",dir:"asc"},
+               MSG_SORTASC:"${_('Click to sort ascending')}",
+               MSG_SORTDESC:"${_('Click to sort descending')}"
+              }
+      );
 
-     F.filterTimeout = null;
-     
-     function set_count(count){
-    
-    	 if(count == 0){
-    		 YUD.setStyle('repos_list','display','none');
-    		 YUD.setStyle('welcome','display','');
-    	 }
-    	 else{
-    		 YUD.setStyle('repos_list','display','');
-    		 YUD.setStyle('welcome','display','none');
-    	 }
-    	 YUD.get('repo_count').innerHTML = count;
-    	 
-     }
-     
-     
-     //set initial count for repos
-     var nodes = S.query('div.table tr td div a.repo_name');
-     
-     set_count(nodes.length)
-     F.updateFilter  = function() { 
-        // Reset timeout 
-        F.filterTimeout = null;
-        
-        var obsolete = [];
-        nodes = S.query('div.table tr td div a.repo_name');
-        var req = q_filter.value.toLowerCase();
-        for (n in nodes){
-            D.setStyle(nodes[n].parentNode.parentNode.parentNode,'display','')
-        }
-        if (req){
-            for (n in nodes){
-                if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
-                    obsolete.push(nodes[n]); 
-                }
-            }
-            if(obsolete){
-                for (n in obsolete){
-                    D.setStyle(obsolete[n].parentNode.parentNode.parentNode,'display','none');
-                }
-            }
-        }
-        // set new count into dashboard
-        set_count(nodes.length - obsolete.length)
-     }
-     
-     E.on(q_filter,'keyup',function(e){
-         clearTimeout(F.filterTimeout); 
-         F.filterTimeout = setTimeout(F.updateFilter,600); 
-     });
-     
+      // main table sorting
+      var myColumnDefs = [
+          {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
+          {key:"name",label:"${_('Name')}",sortable:true,
+              sortOptions: { sortFunction: nameSort }},
+          {key:"desc",label:"${_('Description')}",sortable:true},
+          {key:"last_change",label:"${_('Last Change')}",sortable:true,
+              sortOptions: { sortFunction: ageSort }},
+          {key:"tip",label:"${_('Tip')}",sortable:true,
+        	  sortOptions: { sortFunction: revisionSort }},
+          {key:"owner",label:"${_('Owner')}",sortable:true},
+          {key:"rss",label:"",sortable:false},
+          {key:"atom",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:"desc"},
+              {key:"last_change"},
+              {key:"tip"},
+              {key:"owner"},
+              {key:"rss"},
+              {key:"atom"},
+          ]
+      };
+
+      var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
+              {
+               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...')}",
+              }
+      );
+      myDataTable.subscribe('postRenderEvent',function(oArgs) {
+          tooltip_activate();
+          quick_repo_menu();
+          q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
+      });
+
     </script>
--- a/rhodecode/templates/journal/journal.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/journal/journal.html	Sun Feb 26 17:25:09 2012 +0200
@@ -10,60 +10,211 @@
 	${self.menu('home')}
 </%def>
 <%def name="main()">
-	
+
     <div class="box box-left">
 	    <!-- box / title -->
 	    <div class="title">
 	        <h5>${_('Journal')}</h5>
+             <ul class="links">
+               <li>
+                 <span><a id="refresh" href="${h.url('journal')}"><img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
+                 </a></span>
+               </li>
+             </ul>
 	    </div>
-		<script type="text/javascript">
-		function show_more_event(){
-		YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
-		    var el = e.target;
-		    YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
-		    YUD.setStyle(el.parentNode,'display','none');
-		});
-		}
-		</script> 	    
 	    <div id="journal">${c.journal_data}</div>
     </div>
-    
     <div class="box box-right">
         <!-- box / title -->
         <div class="title">
-            <h5>${_('Following')}</h5>
+            <h5>
+            <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
+            <a id="show_my" class="link-white" href="#my">${_('My repos')}</a> / <a id="show_watched" class="link-white" href="#watched">${_('Watched')}</a>
+            </h5>
+             %if h.HasPermissionAny('hg.admin','hg.create.repository')():
+             <ul class="links">
+               <li>
+                 <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
+               </li>
+             </ul>
+             %endif
+        </div>
+        <!-- end box / title -->
+        <div id="my" class="table">
+            %if c.user_repos:
+            <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">${_('Tip')}</th>
+                  <th class="left">${_('Action')}</th>
+                  <th class="left">${_('Action')}</th>
+             </thead>
+             <tbody>
+                 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
+                 %for repo in c.user_repos:
+                    <tr>
+                        ##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')}
+                            ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
+                          ${h.end_form()}
+                        </td>
+                    </tr>
+                 %endfor
+             </tbody>
+             </table>
+             </div>
+             %else:
+                <div style="padding:5px 0px 10px 0px;">
+                ${_('No repositories yet')}
+                %if h.HasPermissionAny('hg.admin','hg.create.repository')():
+                    ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-btn")}
+                %endif
+                </div>
+             %endif
         </div>
-        <div>
-		%if c.following:
-		    %for entry in c.following:
-		        <div class="currently_following">
-		            %if entry.follows_user_id:
-		              <img title="${_('following user')}" alt="${_('user')}" src="${h.url("/images/icons/user.png")}"/>
-		              ${entry.follows_user.full_contact}
-		            %endif
-		            
-		            %if entry.follows_repo_id:
-		              
-		              <div style="float:left;padding-right:5px">
-                      <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
-                            onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
-                      </span>		            
-		              </div>
-		              %if entry.follows_repository.private:
-		                <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url("/images/icons/lock.png")}"/>
-		              %else:
-		                <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url("/images/icons/lock_open.png")}"/>
-		              %endif
-		              
-		              ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',
-		                repo_name=entry.follows_repository.repo_name))}
-		              
-		            %endif
-		        </div>
-		    %endfor
-		%else:
-		    ${_('You are not following any users or repositories')}
-		%endif
+
+        <div id="watched" class="table" style="display:none">
+          %if c.following:
+            <table>
+            <thead>
+                <tr>
+                <th class="left">${_('Name')}</th>
+            </thead>
+             <tbody>
+                %for entry in c.following:
+                  <tr>
+                    <td>
+                      %if entry.follows_user_id:
+                        <img title="${_('following user')}" alt="${_('user')}" src="${h.url('/images/icons/user.png')}"/>
+                        ${entry.follows_user.full_contact}
+                      %endif
+
+                      %if entry.follows_repo_id:
+                        <div style="float:right;padding-right:5px">
+                        <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
+                              onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
+                        </span>
+                        </div>
+
+                         %if h.is_hg(entry.follows_repository):
+                           <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
+                         %elif h.is_git(entry.follows_repository):
+                           <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
+                         %endif
+
+                        %if entry.follows_repository.private:
+                          <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
+                        %else:
+                          <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
+                        %endif
+                        <span class="watched_repo">
+                            ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))}
+                        </span>
+                      %endif
+                    </td>
+                  </tr>
+                %endfor
+            </tbody>
+            </table>
+          %else:
+              <div style="padding:5px 0px 10px 0px;">
+              ${_('You are not following any users or repositories')}
+              </div>
+          %endif
         </div>
-    </div>    
-</%def>    
+    </div>
+
+    <script type="text/javascript">
+
+    YUE.on('show_my','click',function(e){
+        YUD.setStyle('watched','display','none');
+        YUD.setStyle('my','display','');
+        var nodes = YUQ('#my tr td a.repo_name');
+        var target = 'q_filter';
+        var func = function(node){
+            return node.parentNode.parentNode.parentNode.parentNode;
+        }
+        q_filter(target,nodes,func);
+        YUE.preventDefault(e);
+    })
+    YUE.on('show_watched','click',function(e){
+        YUD.setStyle('my','display','none');
+        YUD.setStyle('watched','display','');
+        var nodes = YUQ('#watched .watched_repo a');
+        var target = 'q_filter';
+        var func = function(node){
+            return node.parentNode.parentNode;
+        }
+        q_filter(target,nodes,func);
+        YUE.preventDefault(e);
+    })
+    YUE.on('refresh','click',function(e){
+        ypjax(e.currentTarget.href,"journal",function(){show_more_event();tooltip_activate();});
+        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 myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
+            {
+              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...')}",
+            }
+    );
+    myDataTable.subscribe('postRenderEvent',function(oArgs) {
+        tooltip_activate();
+        quick_repo_menu();
+        var func = function(node){
+            return node.parentNode.parentNode.parentNode.parentNode;
+        }
+        q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
+    });
+
+
+    </script>
+</%def>
--- a/rhodecode/templates/journal/journal_data.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/journal/journal_data.html	Sun Feb 26 17:25:09 2012 +0200
@@ -20,7 +20,7 @@
 		                              h.url('summary_home',repo_name=entry.repository.repo_name))}
 		                %else:
 		                  ${entry.repository_name}
-		                %endif             
+		                %endif
 		                </span>
 		            </div>
 		            <div class="journal_action_params">${h.literal(h.action_parser(entry)[1]())}</div>
@@ -30,18 +30,20 @@
 	        </div>
         %endfor
     %endfor
-    
-<div class="pagination-wh pagination-left">
-<script type="text/javascript">
-YUE.onDOMReady(function(){
-    YUE.delegate("journal","click",function(e, matchedEl, container){
-    	ypjax(e.target.href,"journal",function(){show_more_event();tooltip_activate();});
-        YUE.preventDefault(e);
-    },'.pager_link');
-});
-</script>
-${c.journal_pager.pager('$link_previous ~2~ $link_next')}
-</div>
+
+  <div class="pagination-wh pagination-left">
+    <script type="text/javascript">
+    YUE.onDOMReady(function(){
+        YUE.delegate("journal","click",function(e, matchedEl, container){
+        	ypjax(e.target.href,"journal",function(){show_more_event();tooltip_activate();});
+            YUE.preventDefault(e);
+        },'.pager_link');
+    });
+    </script>
+  ${c.journal_pager.pager('$link_previous ~2~ $link_next')}
+  </div>
 %else:
-    ${_('No entries yet')}
-%endif
\ No newline at end of file
+  <div style="padding:5px 0px 10px 10px;">
+      ${_('No entries yet')}
+  </div>
+%endif
--- a/rhodecode/templates/journal/public_journal.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/journal/public_journal.html	Sun Feb 26 17:25:09 2012 +0200
@@ -10,7 +10,7 @@
 	${self.menu('home')}
 </%def>
 <%def name="main()">
-	
+
     <div class="box">
 	    <!-- box / title -->
 	    <div class="title">
@@ -21,10 +21,10 @@
                   </li>
                   <li>
                     <span>${h.link_to(_('Atom'),h.url('public_journal_atom'),class_='atom_icon')}</span>
-                  </li>                  
-                  
-                </ul>  	        
-	        
+                  </li>
+
+                </ul>
+
 	    </div>
 		<script type="text/javascript">
 		function show_more_event(){
@@ -34,8 +34,8 @@
 		    YUD.setStyle(el.parentNode,'display','none');
 		});
 		}
-		</script> 	    
+		</script>
 	    <div id="journal">${c.journal_data}</div>
     </div>
-     
-</%def>    
+
+</%def>
--- a/rhodecode/templates/login.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/login.html	Sun Feb 26 17:25:09 2012 +0200
@@ -15,12 +15,12 @@
         % endfor
     </ul>
     % endif
-</div>          
+</div>
     <!-- login -->
     <div class="title top-left-rounded-corner top-right-rounded-corner">
         <h5>${_('Sign In to')} ${c.rhodecode_name}</h5>
     </div>
-    <div class="inner">            
+    <div class="inner">
         ${h.form(h.url.current(came_from=c.came_from))}
         <div class="form">
             <!-- fields -->
@@ -33,8 +33,8 @@
                     <div class="input">
                         ${h.text('username',class_='focus',size=40)}
                     </div>
-                    
-                </div>                     
+
+                </div>
                 <div class="field">
                     <div class="label">
                         <label for="password">${_('Password')}:</label>
@@ -42,14 +42,14 @@
                     <div class="input">
                         ${h.password('password',class_='focus',size=40)}
                     </div>
-                    
+
                 </div>
-                ##<div class="field">
-                ##    <div class="checkbox">
-                ##        <input type="checkbox" id="remember" name="remember" />
-                ##        <label for="remember">Remember me</label>
-                ##    </div>
-                ##</div>
+                <div class="field">
+                    <div class="checkbox">
+                        <input type="checkbox" id="remember" name="remember" />
+                        <label for="remember">${_('Remember me')}</label>
+                    </div>
+                </div>
                 <div class="buttons">
                     ${h.submit('sign_in',_('Sign In'),class_="ui-button")}
                 </div>
@@ -59,7 +59,7 @@
             <div class="links">
                 ${h.link_to(_('Forgot your password ?'),h.url('reset_password'))}
                 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
-                  / 
+                  /
                  ${h.link_to(_("Don't have an account ?"),h.url('register'))}
                 %endif
             </div>
--- a/rhodecode/templates/password_reset.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/password_reset.html	Sun Feb 26 17:25:09 2012 +0200
@@ -6,7 +6,7 @@
 </%def>
 
 <div id="register">
-	
+
 	<div class="title top-left-rounded-corner top-right-rounded-corner">
 		<h5>${_('Reset your password to')} ${c.rhodecode_name}</h5>
 	</div>
@@ -15,7 +15,7 @@
 	    <div class="form">
 	        <!-- fields -->
 	        <div class="fields">
-	            
+
 	             <div class="field">
 	                <div class="label">
 	                    <label for="email">${_('Email address')}:</label>
@@ -24,13 +24,13 @@
 	                    ${h.text('email')}
 	                </div>
 	             </div>
-	                        
+
 	            <div class="buttons">
 		            <div class="nohighlight">
 		              ${h.submit('send',_('Reset my password'),class_="ui-button")}
 					  	<div class="activation_msg">${_('Password reset link will be send to matching email address')}</div>
 		            </div>
-	            </div>             
+	            </div>
 	    	</div>
 	    </div>
 	    ${h.end_form()}
@@ -38,7 +38,6 @@
         YUE.onDOMReady(function(){
             YUD.get('email').focus();
         })
-        </script>	    
-	</div>    
+        </script>
+	</div>
    </div>
-
--- a/rhodecode/templates/register.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/register.html	Sun Feb 26 17:25:09 2012 +0200
@@ -4,9 +4,9 @@
 <%def name="title()">
     ${_('Sign Up')} - ${c.rhodecode_name}
 </%def>
-    
+
 <div id="register">
-	
+
 	<div class="title top-left-rounded-corner top-right-rounded-corner">
 		<h5>${_('Sign Up to')} ${c.rhodecode_name}</h5>
 	</div>
@@ -23,7 +23,7 @@
 	                    ${h.text('username',class_="medium")}
 	                </div>
 	             </div>
-	            
+
 	             <div class="field">
 	                <div class="label">
 	                    <label for="password">${_('Password')}:</label>
@@ -32,7 +32,7 @@
 	                    ${h.password('password',class_="medium")}
 	                </div>
 	             </div>
-                       
+
                        <div class="field">
                           <div class="label">
                               <label for="password">${_('Re-enter password')}:</label>
@@ -41,7 +41,7 @@
                               ${h.password('password_confirmation',class_="medium")}
                           </div>
                        </div>
-                       			            
+
 	             <div class="field">
 	                <div class="label">
 	                    <label for="name">${_('First Name')}:</label>
@@ -50,7 +50,7 @@
 	                    ${h.text('name',class_="medium")}
 	                </div>
 	             </div>
-	            
+
 	             <div class="field">
 	                <div class="label">
 	                    <label for="lastname">${_('Last Name')}:</label>
@@ -59,7 +59,7 @@
 	                    ${h.text('lastname',class_="medium")}
 	                </div>
 	             </div>
-	            
+
 	             <div class="field">
 	                <div class="label">
 	                    <label for="email">${_('Email')}:</label>
@@ -68,7 +68,7 @@
 	                    ${h.text('email',class_="medium")}
 	                </div>
 	             </div>
-	                        
+
 	            <div class="buttons">
 		            <div class="nohighlight">
 		              ${h.submit('sign_up',_('Sign Up'),class_="ui-button")}
@@ -78,7 +78,7 @@
 					  	<div class="activation_msg">${_('Your account must wait for activation by administrator')}</div>
 					  %endif
 		            </div>
-	            </div>             
+	            </div>
 	    	</div>
 	    </div>
 	    ${h.end_form()}
@@ -86,7 +86,6 @@
         YUE.onDOMReady(function(){
             YUD.get('username').focus();
         })
-        </script>	    
-	</div>    
+        </script>
+	</div>
  </div>
-
--- a/rhodecode/templates/repo_switcher_list.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/repo_switcher_list.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,23 +1,20 @@
 ## -*- coding: utf-8 -*-
 
 <li class="qfilter_rs">
-<input type="text" 
-style="border:0"        
-value="quick filter..." 
-name="filter" size="15" id="q_filter_rs" />
+    <input type="text" style="border:0" value="quick filter..." name="filter" size="15" id="q_filter_rs" />
 </li>
-    
+
 %for repo in c.repos_list:
-     
+
       %if repo['dbrepo']['private']:
          <li>
-             <img src="${h.url("/images/icons/lock.png")}" alt="${_('Private repository')}" class="repo_switcher_type"/>
+             <img src="${h.url('/images/icons/lock.png')}" alt="${_('Private repository')}" class="repo_switcher_type"/>
              ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="repo_name %s" % repo['dbrepo']['repo_type'])}
           </li>
       %else:
          <li>
-             <img src="${h.url("/images/icons/lock_open.png")}" alt="${_('Public repository')}" class="repo_switcher_type" />
+             <img src="${h.url('/images/icons/lock_open.png')}" alt="${_('Public repository')}" class="repo_switcher_type" />
              ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="repo_name %s" % repo['dbrepo']['repo_type'])}
          </li>
-      %endif  
-%endfor
\ No newline at end of file
+      %endif
+%endfor
--- a/rhodecode/templates/search/search.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/search/search.html	Sun Feb 26 17:25:09 2012 +0200
@@ -2,11 +2,11 @@
 <%inherit file="/base/base.html"/>
 <%def name="title()">
    ${_('Search')}
-   ${'"%s"' % c.cur_query if c.cur_query else None} 
+   ${'"%s"' % c.cur_query if c.cur_query else None}
 	%if c.repo_name:
 		${_('in repository: ') + c.repo_name}
 	%else:
-		${_('in all repositories')}		
+		${_('in all repositories')}
 	%endif
 	- ${c.rhodecode_name}
 </%def>
@@ -26,12 +26,12 @@
 			${_('in repository: ') + c.repo_name}
 		%else:
 			${_('in all repositories')}
-		%endif		
+		%endif
 		</h5>
 	</div>
 	<!-- end box / title -->
 	%if c.repo_name:
-		${h.form(h.url('search_repo',search_repo=c.repo_name),method='get')}	
+		${h.form(h.url('search_repo',search_repo=c.repo_name),method='get')}
 	%else:
 		${h.form(h.url('search'),method='get')}
 	%endif
@@ -40,13 +40,13 @@
 			<div class="field field-first field-noborder">
              <div class="label">
                  <label for="q">${_('Search term')}</label>
-             </div> 			
+             </div>
 				<div class="input">${h.text('q',c.cur_query,class_="small")}
 					<div class="button highlight">
 						<input type="submit" value="${_('Search')}" class="ui-button"/>
 					</div>
 				</div>
-				<div style="font-weight: bold;clear:Both;margin-left:200px">${c.runtime}</div>		
+				<div style="font-weight: bold;clear:Both;margin-left:200px">${c.runtime}</div>
 			</div>
 
 			<div class="field">
@@ -58,14 +58,14 @@
                         ##('commit',_('Commit messages')),
                         ('path',_('File names')),
                         ##('repository',_('Repository names')),
-                        ])} 
+                        ])}
                 </div>
              </div>
-			             
+
 		</div>
 	</div>
 	${h.end_form()}
-	
+
     %if c.cur_search == 'content':
         <%include file='search_content.html'/>
     %elif c.cur_search == 'path':
@@ -74,7 +74,7 @@
         <%include file='search_commit.html'/>
     %elif c.cur_search == 'repository':
         <%include file='search_repository.html'/>
-    %endif                            
+    %endif
 </div>
 
-</%def>    
+</%def>
--- a/rhodecode/templates/search/search_content.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/search/search_content.html	Sun Feb 26 17:25:09 2012 +0200
@@ -5,10 +5,11 @@
     <div class="table">
         <div id="body${cnt}" class="codeblock">
             <div class="code-header">
-                <div class="revision">${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
-                h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}</div>
+                <div class="search-path">${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
+                h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}
+                </div>
             </div>
-            <div class="code-body">
+            <div class="search-code-body">
                 <pre>${h.literal(sr['content_short_hl'])}</pre>
             </div>
         </div>
@@ -19,13 +20,13 @@
             <div id="body${cnt}" class="codeblock">
                 <div class="error">${_('Permission denied')}</div>
             </div>
-        </div>      
+        </div>
         %endif
-        
-    %endif      
+
+    %endif
 %endfor
 %if c.cur_query and c.formated_results:
 <div class="pagination-wh pagination-left" style="padding-left:16px">
     ${c.formated_results.pager('$link_previous ~2~ $link_next')}
-</div>  
-%endif
\ No newline at end of file
+</div>
+%endif
--- a/rhodecode/templates/search/search_path.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/search/search_path.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,27 +1,26 @@
 ##path search
-<div class="search">
-	%for cnt,sr in enumerate(c.formated_results):
-	    %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(sr['repository'],'search results check'):
-		    <div class="search_path">
+
+%for cnt,sr in enumerate(c.formated_results):
+    %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(sr['repository'],'search results check'):
+	    <div class="search_path">
+	        <div class="link">
+	            ${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
+	                h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}
+	        </div>
+	    </div>
+    %else:
+        %if cnt == 0:
+		    <div class="error">
 		        <div class="link">
-		            ${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
-		                h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}        
+		            ${_('Permission denied')}
 		        </div>
 		    </div>
-	    %else:
-	        %if cnt == 0:
-			    <div class="error">
-			        <div class="link">
-			            ${_('Permission denied')}        
-			        </div>
-			    </div>        
-	        %endif
-	        
-	    %endif      
-	%endfor
-	%if c.cur_query and c.formated_results:
-	<div class="pagination-wh pagination-left">
-	    ${c.formated_results.pager('$link_previous ~2~ $link_next')}
-	</div>  
-	%endif
-</div>
\ No newline at end of file
+        %endif
+
+    %endif
+%endfor
+%if c.cur_query and c.formated_results:
+<div class="pagination-wh pagination-left">
+    ${c.formated_results.pager('$link_previous ~2~ $link_next')}
+</div>
+%endif
--- a/rhodecode/templates/settings/repo_fork.html	Sun Feb 19 20:21:14 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%def name="title()">
-    ${c.repo_name} ${_('Fork')} - ${c.rhodecode_name}
-</%def>
-
-<%def name="breadcrumbs_links()">
-    ${h.link_to(u'Home',h.url('/'))}
-    &raquo;
-    ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))} 
-    &raquo;
-    ${_('fork')}
-</%def>
-
-<%def name="page_nav()">
-	${self.menu('')}
-</%def>
-<%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}      
-    </div>
-    ${h.form(url('repo_fork_create_home',repo_name=c.repo_info.repo_name))}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-            <div class="field">
-	            <div class="label">
-	                <label for="repo_name">${_('Fork name')}:</label>
-	            </div>
-	            <div class="input">
-	                ${h.text('fork_name',class_="small")}
-	                ${h.hidden('repo_type',c.repo_info.repo_type)}
-	            </div>
-             </div>
-            <div class="field">
-                <div class="label label-textarea">
-                    <label for="description">${_('Description')}:</label>
-                </div>
-                <div class="textarea text-area editor">
-                    ${h.textarea('description',cols=23,rows=5)}
-                </div>
-             </div>
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="private">${_('Private')}:</label>
-                </div>
-                <div class="checkboxes">
-                    ${h.checkbox('private',value="True")}
-                </div>
-             </div>
-	        <div class="buttons">
-	          ${h.submit('',_('fork this repository'),class_="ui-button")}
-	        </div>                                                          
-        </div>
-    </div>    
-    ${h.end_form()}    
-</div>
-</%def>   
--- a/rhodecode/templates/settings/repo_settings.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/settings/repo_settings.html	Sun Feb 26 17:25:09 2012 +0200
@@ -8,9 +8,9 @@
 <%def name="breadcrumbs_links()">
     ${h.link_to(u'Home',h.url('/'))}
     &raquo;
-    ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))} 
-    &raquo; 
-    ${_('Settings')} 
+    ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
+    &raquo;
+    ${_('Settings')}
 </%def>
 
 <%def name="page_nav()">
@@ -20,7 +20,7 @@
 <div class="box">
     <!-- box / title -->
     <div class="title">
-        ${self.breadcrumbs()}      
+        ${self.breadcrumbs()}
     </div>
     ${h.form(url('repo_settings_update', repo_name=c.repo_info.repo_name),method='put')}
     <div class="form">
@@ -41,7 +41,7 @@
                 <div class="input">
                     ${h.select('repo_group','',c.repo_groups,class_="medium")}
                 </div>
-            </div>             
+            </div>
             <div class="field">
                 <div class="label label-textarea">
                     <label for="description">${_('Description')}:</label>
@@ -50,7 +50,7 @@
                     ${h.textarea('description',cols=23,rows=5)}
                 </div>
             </div>
-            
+
             <div class="field">
                 <div class="label label-checkbox">
                     <label for="private">${_('Private')}:</label>
@@ -59,7 +59,7 @@
                     ${h.checkbox('private',value="True")}
                 </div>
             </div>
-             
+
              <div class="field">
                 <div class="label">
                     <label for="">${_('Permissions')}:</label>
@@ -67,16 +67,14 @@
                 <div class="input">
                     <%include file="../admin/repos/repo_edit_perms.html"/>
                 </div>
-             
+
             <div class="buttons">
               ${h.submit('save','Save',class_="ui-button")}
               ${h.reset('reset','Reset',class_="ui-button")}
-            </div>                                                          
+            </div>
         </div>
-    </div>    
+    </div>
     ${h.end_form()}
 </div>
 </div>
-</%def> 
-       
-   
+</%def>
--- a/rhodecode/templates/shortlog/shortlog.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/shortlog/shortlog.html	Sun Feb 26 17:25:09 2012 +0200
@@ -8,7 +8,7 @@
 
 <%def name="breadcrumbs_links()">
     ${h.link_to(u'Home',h.url('/'))}
-    &raquo; 
+    &raquo;
     ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
     &raquo;
     ${_('shortlog')}
@@ -29,5 +29,5 @@
 	        ${c.shortlog_data}
 	    </div>
     </div>
-</div>    
-</%def>
\ No newline at end of file
+</div>
+</%def>
--- a/rhodecode/templates/shortlog/shortlog_data.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/shortlog/shortlog_data.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,31 +1,35 @@
 ## -*- coding: utf-8 -*-
-% if c.repo_changesets:
-<table>
+%if c.repo_changesets:
+<table class="table_disp">
 	<tr>
-		<th class="left">${_('commit message')}</th>
+	    <th class="left">${_('revision')}</th>
+        <th class="left">${_('commit message')}</th>
 		<th class="left">${_('age')}</th>
 		<th class="left">${_('author')}</th>
-		<th class="left">${_('revision')}</th>
 		<th class="left">${_('branch')}</th>
 		<th class="left">${_('tags')}</th>
-		<th class="left">${_('links')}</th>
-		
 	</tr>
 %for cnt,cs in enumerate(c.repo_changesets):
 	<tr class="parity${cnt%2}">
         <td>
+            <div><pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}">r${cs.revision}:${h.short_id(cs.raw_id)}</a></pre></div>
+        </td>
+        <td>
             ${h.link_to(h.truncate(cs.message,50),
             h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id),
             title=cs.message)}
         </td>
         <td><span class="tooltip" title="${cs.date}">
                       ${h.age(cs.date)}</span>
-        </td>        	
+        </td>
 		<td title="${cs.author}">${h.person(cs.author)}</td>
-		<td>r${cs.revision}:${h.short_id(cs.raw_id)}</td>
 		<td>
 			<span class="logtags">
-				<span class="branchtag">${cs.branch}</span>
+				<span class="branchtag">
+                %if h.is_hg(c.rhodecode_repo):
+                    ${cs.branch}
+                %endif
+                </span>
 			</span>
 		</td>
 		<td>
@@ -35,11 +39,6 @@
 				%endfor
 			</span>
 		</td>
-		<td class="nowrap">
-		${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
-		|
-		${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
-		</td>
 	</tr>
 %endfor
 
@@ -50,7 +49,7 @@
     YUE.delegate("shortlog_data","click",function(e, matchedEl, container){
         ypjax(e.target.href,"shortlog_data",function(){tooltip_activate();});
         YUE.preventDefault(e);
-    },'.pager_link');	  
+    },'.pager_link');
   });
 </script>
 
@@ -58,5 +57,27 @@
 ${c.repo_changesets.pager('$link_previous ~2~ $link_next')}
 </div>
 %else:
-    ${_('There are no changes yet')}
+
+%if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
+<h4>${_('Add or upload files directly via RhodeCode')}</h4>
+<div style="margin: 20px 30px;">
+  <div id="add_node_id" class="add_node">
+      <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='')}">${_('add new file')}</a>
+  </div>
+</div>
 %endif
+
+
+<h4>${_('Push new repo')}</h4>
+<pre>
+    ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
+    ${c.rhodecode_repo.alias} add README # add first file
+    ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
+    ${c.rhodecode_repo.alias} push # push changes back
+</pre>
+
+<h4>${_('Existing repository?')}</h4>
+<pre>
+    ${c.rhodecode_repo.alias} push ${c.clone_repo_url}
+</pre>
+%endif
--- a/rhodecode/templates/summary/summary.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/summary/summary.html	Sun Feb 26 17:25:09 2012 +0200
@@ -6,18 +6,25 @@
 
 <%def name="breadcrumbs_links()">
     ${h.link_to(u'Home',h.url('/'))}
-    &raquo; 
+    &raquo;
     ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
     &raquo;
     ${_('summary')}
 </%def>
 
 <%def name="page_nav()">
-	${self.menu('summary')}    
+	${self.menu('summary')}
 </%def>
 
 <%def name="main()">
-<div class="box box-left">
+    <%
+    summary = lambda n:{False:'summary-short'}.get(n)
+    %>
+    %if c.show_stats:
+        <div class="box box-left">
+    %else:
+        <div class="box">
+    %endif
     <!-- box / title -->
     <div class="title">
         ${self.breadcrumbs()}
@@ -25,81 +32,81 @@
     <!-- end box / title -->
 	<div class="form">
 	  <div id="summary" class="fields">
-		 
+
 			 <div class="field">
-			  <div class="label">
+			  <div class="label-summary">
 			      <label>${_('Name')}:</label>
 			  </div>
-			  <div class="input-short">
+			  <div class="input ${summary(c.show_stats)}">
+                  <div style="float:right;padding:5px 0px 0px 5px">
+                     %if c.rhodecode_user.username != 'default':
+                      ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
+                      ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
+                     %else:
+                      ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
+                      ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
+                     %endif
+                  </div>
                   %if c.rhodecode_user.username != 'default':
                       %if c.following:
                       <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
                             onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
-                      </span>                 
+                      </span>
                       %else:
                       <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
                             onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
                       </span>
                       %endif
-                  %endif:			  
-                 
+                  %endif:
                  ##REPO TYPE
-		         %if c.dbrepo.repo_type =='hg':
+		         %if h.is_hg(c.dbrepo):
 		           <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
 		         %endif
-		         %if c.dbrepo.repo_type =='git':
+		         %if h.is_git(c.dbrepo):
 		           <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
-		         %endif 
-                            
-                 ##PUBLIC/PRIVATE     			  
+		         %endif
+
+                 ##PUBLIC/PRIVATE
 	             %if c.dbrepo.private:
 	                <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
 	             %else:
 	                <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
 	             %endif
-	             
+
 	              ##REPO NAME
-			      <span class="repo_name">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
-                  
+			      <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
+
                   ##FORK
 		          %if c.dbrepo.fork:
 	            	<div style="margin-top:5px;clear:both"">
-	            	<a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
-	            	<img class="icon" alt="${_('public')}"
-	            	title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" 
-	            	src="${h.url('/images/icons/arrow_divide.png')}"/>
-	            	${_('Fork of')} ${c.dbrepo.fork.repo_name}
+	            	<a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
+	            	    ${_('Fork of')} ${c.dbrepo.fork.repo_name}
 	            	</a>
 	            	</div>
 		          %endif
 		          ##REMOTE
 				  %if c.dbrepo.clone_uri:
                     <div style="margin-top:5px;clear:both">
-                    <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}">
-                    <img class="icon" alt="${_('remote clone')}"
-                    title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" 
-                    src="${h.url('/images/icons/connect.png')}"/>
-                    ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
+                    <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
+                        ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
                     </a>
-                    </div>					
-				  %endif		            		      
+                    </div>
+				  %endif
 			  </div>
 			 </div>
-			
-			
+
 			 <div class="field">
-			  <div class="label">
+			  <div class="label-summary">
 			      <label>${_('Description')}:</label>
 			  </div>
-			  <div class="input-short desc">${h.urlify_text(c.dbrepo.description)}</div>
+			  <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
 			 </div>
-			
-			
+
 			 <div class="field">
-			  <div class="label">
+			  <div class="label-summary">
 			      <label>${_('Contact')}:</label>
 			  </div>
-			  <div class="input-short">
+			  <div class="input ${summary(c.show_stats)}">
 			  	<div class="gravatar">
 			  		<img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
 			  	</div>
@@ -108,595 +115,584 @@
 			  		${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
 			  </div>
 			 </div>
-			
+
 			 <div class="field">
-			  <div class="label">
-			      <label>${_('Last change')}:</label>
-			  </div>
-			  <div class="input-short">
-                  <b>${'r%s:%s' % (h.get_changeset_safe(c.rhodecode_repo,'tip').revision,
-                            h.get_changeset_safe(c.rhodecode_repo,'tip').short_id)}</b> - 
-			      <span class="tooltip" title="${c.rhodecode_repo.last_change}">
-			      ${h.age(c.rhodecode_repo.last_change)}</span><br/>
-			      ${_('by')} ${h.get_changeset_safe(c.rhodecode_repo,'tip').author} 
-			      
-			  </div>
-			 </div>
-			
-			 <div class="field">
-			  <div class="label">
+			  <div class="label-summary">
 			      <label>${_('Clone url')}:</label>
 			  </div>
-			  <div class="input-short">
-			      <input type="text" id="clone_url" readonly="readonly" value="${c.rhodecode_repo.alias} clone ${c.clone_repo_url}" size="70"/>
+			  <div class="input ${summary(c.show_stats)}">
+                  <div  style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
+                  <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
+			      <input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
+                  <input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
 			  </div>
 			 </div>
-			 
+
 			 <div class="field">
-			  <div class="label">
-			      <label>${_('Trending source files')}:</label>
+			  <div class="label-summary">
+			      <label>${_('Trending files')}:</label>
 			  </div>
-			  <div class="input-short">
-			    <div id="lang_stats"></div> 			   
+			  <div class="input ${summary(c.show_stats)}">
+                %if c.show_stats:
+			    <div id="lang_stats"></div>
+                %else:
+                   ${_('Statistics are disabled for this repository')}
+                   %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
+                        ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
+                   %endif
+                %endif
 			  </div>
 			 </div>
-			 			
+
 			 <div class="field">
-			  <div class="label">
+			  <div class="label-summary">
 			      <label>${_('Download')}:</label>
 			  </div>
-			  <div class="input-short">
+			  <div class="input ${summary(c.show_stats)}">
 		        %if len(c.rhodecode_repo.revisions) == 0:
 		          ${_('There are no downloads yet')}
 		        %elif c.enable_downloads is False:
 		          ${_('Downloads are disabled for this repository')}
-                    %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
-                        ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-button-small")}
-                    %endif  		          
+                    %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
+                        ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
+                    %endif
 		        %else:
 			        ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
-			        %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
-			             %if cnt >=1:
-			             |
-			             %endif
-			             <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}" 
-			                  id="${archive['type']+'_link'}">${h.link_to(archive['type'],
-			                h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
-			                fname='tip'+archive['extension']),class_="archive_icon")}</span>
-			        %endfor
+			             <span id="${'zip_link'}">${h.link_to('Download as zip',h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
                     <span style="vertical-align: bottom">
-                        <input id="archive_subrepos" type="checkbox" name="subrepos"/> <span class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('with subrepos')}</span>
+                        <input id="archive_subrepos" type="checkbox" name="subrepos" />
+                        <label for="archive_subrepos" class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('with subrepos')}</label>
                     </span>
 			    %endif
 			  </div>
 			 </div>
-			 
-			 <div class="field">
-			  <div class="label">
-			      <label>${_('Feeds')}:</label>
-			  </div>
-			  <div class="input-short">
-			   %if c.rhodecode_user.username != 'default':
-	            ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
-	            ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
-	           %else:
-                ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
-                ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}	           
-	           %endif 
-			  </div>
-			 </div>				 			 			 
-	  </div>		 
+	  </div>
 	</div>
-  	<script type="text/javascript">
-	  	YUE.onDOMReady(function(e){
-	  	    id = 'clone_url';
-	  	    YUE.on(id,'click',function(e){
-	  	    	if(YUD.hasClass(id,'selected')){
-	  	    		return
-	  	    	}
-	  	    	else{
-	                YUD.addClass(id,'selected');
-	                YUD.get(id).select();	  	    		
-	  	    	}
-
-	  	    })
-	  	})
-        var data = ${c.trending_languages|n};
-        var total = 0;
-        var no_data = true;
-        var tbl = document.createElement('table');
-        tbl.setAttribute('class','trending_language_tbl');
-        var cnt = 0;
-        
-        for (var i=0;i<data.length;i++){
-            total += data[i][1].count;    
-            cnt += 1;
-            no_data = false;
-            
-            var hide = cnt>2;
-            var tr = document.createElement('tr');
-            if (hide){
-                tr.setAttribute('style','display:none');
-                tr.setAttribute('class','stats_hidden');
-            }
-            var k = data[i][0];
-            var obj = data[i][1];
-            var percentage = Math.round((obj.count/total*100),2);
-            
-            var td1 = document.createElement('td');
-            td1.width = 150;
-            var trending_language_label = document.createElement('div');
-            trending_language_label.innerHTML = obj.desc+" ("+k+")";
-            td1.appendChild(trending_language_label);
-
-            var td2 = document.createElement('td');
-            td2.setAttribute('style','padding-right:14px !important');
-            var trending_language = document.createElement('div');
-            var nr_files = obj.count+" ${_('files')}";
-
-            trending_language.title = k+" "+nr_files;
+</div>
 
-            if (percentage>22){
-                trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
-            }
-            else{
-                trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
-            }
-
-            trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
-            trending_language.style.width=percentage+"%";
-            td2.appendChild(trending_language);
-
-            tr.appendChild(td1);
-            tr.appendChild(td2);
-            tbl.appendChild(tr);
-            if(cnt == 3){
-                var show_more = document.createElement('tr');
-                var td = document.createElement('td');
-                lnk = document.createElement('a');
-
-                lnk.href='#';
-                lnk.innerHTML = "${_('show more')}";
-                lnk.id='code_stats_show_more';
-                td.appendChild(lnk);
-
-                show_more.appendChild(td);
-                show_more.appendChild(document.createElement('td'));
-                tbl.appendChild(show_more);
-            }
-
-        }
-  		if(no_data){
-  			var tr = document.createElement('tr');
-  			var td1 = document.createElement('td');
-  			td1.innerHTML = "${c.no_data_msg}";
-  			tr.appendChild(td1);
-  			tbl.appendChild(tr);
-		}
-  		YUD.get('lang_stats').appendChild(tbl);
-  		YUE.on('code_stats_show_more','click',function(){
-  			l = YUD.getElementsByClassName('stats_hidden')
-  			for (e in l){
-  			    YUD.setStyle(l[e],'display','');
-  			};
-  			YUD.setStyle(YUD.get('code_stats_show_more'),
-  					'display','none');
-  		})
-  	
-             var tmpl_links = {}
-              %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
-                tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
-                     h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
-                     fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_="archive_icon")}';
-              %endfor
-              
-             YUE.on(['download_options','archive_subrepos'],'change',function(e){
-            	 var sm = YUD.get('download_options');
-                 var new_cs = sm.options[sm.selectedIndex];
-                 
-                 for(k in tmpl_links){
-                	 var s = YUD.get(k+'_link');
-                	 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
-                	 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text);
-                	 s.title = s.title.replace('__CS_EXT__',k);
-                	 var url = tmpl_links[k].replace('__CS__',new_cs.value);
-                	 var subrepos = YUD.get('archive_subrepos').checked
-                	 url = url.replace('__SUB__',subrepos);
-                	 s.innerHTML = url 
-                 }
-             });
-  	</script>    				
-</div>
-        
+%if c.show_stats:
 <div class="box box-right"  style="min-height:455px">
     <!-- box / title -->
     <div class="title">
         <h5>${_('Commit activity by day / author')}</h5>
     </div>
-    
+
     <div class="graph">
-         <div style="padding:0 10px 10px 15px;font-size: 1.2em;">
+         <div style="padding:0 10px 10px 17px;">
          %if c.no_data:
            ${c.no_data_msg}
            %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
-                ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-button-small")}
-           %endif         
-           
+                ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
+           %endif
         %else:
-            ${_('Loaded in')} ${c.stats_percentage} %
+            ${_('Stats gathered: ')} ${c.stats_percentage}%
         %endif
-        </div>  
+        </div>
         <div id="commit_history" style="width:450px;height:300px;float:left"></div>
         <div style="clear: both;height: 10px"></div>
         <div id="overview" style="width:450px;height:100px;float:left"></div>
-        
+
     	<div id="legend_data" style="clear:both;margin-top:10px;">
 	    	<div id="legend_container"></div>
 	    	<div id="legend_choices">
-				<table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
+				<table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
 	    	</div>
     	</div>
-		<script type="text/javascript">
-		/**
-		 * Plots summary graph
-		 *
-		 * @class SummaryPlot
-		 * @param {from} initial from for detailed graph
-		 * @param {to} initial to for detailed graph
-		 * @param {dataset}
-		 * @param {overview_dataset}
-		 */
-		function SummaryPlot(from,to,dataset,overview_dataset) {
-			var initial_ranges = {
-			    "xaxis":{
-				    "from":from,
-				   	"to":to,
-				},
-			};
-		    var dataset = dataset;
-		    var overview_dataset = [overview_dataset];
-		    var choiceContainer = YUD.get("legend_choices");
-		    var choiceContainerTable = YUD.get("legend_choices_tables");
-		    var plotContainer = YUD.get('commit_history');
-		    var overviewContainer = YUD.get('overview');
-		    
-		    var plot_options = {
-				bars: {show:true,align:'center',lineWidth:4},
-				legend: {show:true, container:"legend_container"},
-				points: {show:true,radius:0,fill:false},
-				yaxis: {tickDecimals:0,},
-				xaxis: {
-					mode: "time", 
-					timeformat: "%d/%m",
-				    min:from,
-				    max:to,	
-				}, 
-				grid: {
-					hoverable: true, 
-				    clickable: true,
-				    autoHighlight:true,
-				    color: "#999"
-				},
-				//selection: {mode: "x"}
-		    };
-		    var overview_options = {
-				legend:{show:false},
-			    bars: {show:true,barWidth: 2,},
-			    shadowSize: 0,
-			    xaxis: {mode: "time", timeformat: "%d/%m/%y",},
-			    yaxis: {ticks: 3, min: 0,tickDecimals:0,},
-			    grid: {color: "#999",},
-			    selection: {mode: "x"}
-			};
-
-			/**
-			*get dummy data needed in few places
-			*/
-		    function getDummyData(label){
-		    	return {"label":label,
-               	 "data":[{"time":0,
-               		 "commits":0,
-	                     "added":0,
-	                     "changed":0,
-	                     "removed":0,
-                    }],
-                    "schema":["commits"],
-                    "color":'#ffffff',
-           		}
-			}
-			
-		    /**
-		     * generate checkboxes accordindly to data
-		     * @param keys
-		     * @returns
-		     */
-		    function generateCheckboxes(data) {
-			    //append checkboxes
-			    var i = 0;
-			    choiceContainerTable.innerHTML = '';
-			    for(var pos in data) {
-			    	
-			    	data[pos].color = i;
-			        i++;
-			        if(data[pos].label != ''){
-				        choiceContainerTable.innerHTML += '<tr><td>'+
-				        '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
-				        +data[pos].label+
-				        '</td></tr>';
-			        }
-			    }	
-		    }
-		    
-		    /**
-		     * ToolTip show
-		     */
-		    function showTooltip(x, y, contents) {
-		        var div=document.getElementById('tooltip');
-		        if(!div) {
-		            div = document.createElement('div');
-		            div.id="tooltip";
-		            div.style.position="absolute";
-		            div.style.border='1px solid #fdd';
-		            div.style.padding='2px';
-		            div.style.backgroundColor='#fee';
-		            document.body.appendChild(div);
-		        }
-		        YUD.setStyle(div, 'opacity', 0);
-		        div.innerHTML = contents;
-		        div.style.top=(y + 5) + "px";
-		        div.style.left=(x + 5) + "px";
-
-		        var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
-		        anim.animate();
-		    }
-		    
-			/**
-			 * This function will detect if selected period has some changesets 
-			   for this user if it does this data is then pushed for displaying
-			   Additionally it will only display users that are selected by the checkbox
-			*/
-		    function getDataAccordingToRanges(ranges) {
-		    	
-		        var data = [];
-		        var new_dataset = {};
-		        var keys = [];
-		        var max_commits = 0;
-				for(var key in dataset){
-					
-		            for(var ds in dataset[key].data){
-			            commit_data = dataset[key].data[ds];
-			            if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
-
-			            	if(new_dataset[key] === undefined){
-			            		new_dataset[key] = {data:[],schema:["commits"],label:key};	
-			            	}
-			            	new_dataset[key].data.push(commit_data);
-					    }
-				    }
-		            if (new_dataset[key] !== undefined){
-		            	data.push(new_dataset[key]);	
-		            }
-				}
-
-				if (data.length > 0){
-					return data;	
-				}
-				else{
-					//just return dummy data for graph to plot itself
-					return [getDummyData('')];	
-				}
-		    }
-		    
-			/**
-			* redraw using new checkbox data
-			*/
-		    function plotchoiced(e,args){
-			    var cur_data = args[0];
-			    var cur_ranges = args[1];
-		    	
-				var new_data = [];
-		    	var inputs = choiceContainer.getElementsByTagName("input");
+    </div>
+</div>
+%endif
 
-		    	//show only checked labels
-		        for(var i=0; i<inputs.length; i++) {
-		            var checkbox_key = inputs[i].name;
-		            
-	                if(inputs[i].checked){
-						for(var d in cur_data){
-							if(cur_data[d].label == checkbox_key){
-								new_data.push(cur_data[d]);
-							}
-						}			                
-	    	        }
-	                else{
-		                //push dummy data to not hide the label
-						new_data.push(getDummyData(checkbox_key));
-			        }
-		        }
-					        
-		    	var new_options = YAHOO.lang.merge(plot_options, {
-		            xaxis: { 
-		  	      		min: cur_ranges.xaxis.from, 
-		  	      		max: cur_ranges.xaxis.to,
-		  	      		mode:"time",
-		  	      		timeformat: "%d/%m",
-		        	},
-		    	});
-		    	if (!new_data){
-					new_data = [[0,1]];
-				}
-		    	// do the zooming
-		       plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
-		       
-		       plot.subscribe("plotselected", plotselected);
-	
-		       //resubscribe plothover
-		       plot.subscribe("plothover", plothover);
-		        
-		       // don't fire event on the overview to prevent eternal loop 
-		       overview.setSelection(cur_ranges, true);
-	
-		    }
-		    
-			/**
-		     * plot only selected items from overview
-		     * @param ranges
-		     * @returns
-		     */
-		    function plotselected(ranges,cur_data) {
-			    //updates the data for new plot
-	    		var data = getDataAccordingToRanges(ranges);
-	    		generateCheckboxes(data);
-	    		
-		    	var new_options = YAHOO.lang.merge(plot_options, {
-		            xaxis: { 
-		  	      		min: ranges.xaxis.from, 
-		  	      		max: ranges.xaxis.to,
-		  	      		mode:"time",
-		  	      		timeformat: "%d/%m",
-		        	},
-		    	});
-		    	// do the zooming 
-		        plot = YAHOO.widget.Flot(plotContainer, data, new_options);
-
-		        plot.subscribe("plotselected", plotselected);
-
-		        //resubscribe plothover
-		        plot.subscribe("plothover", plothover);
-		        
-		        // don't fire event on the overview to prevent eternal loop
-		        overview.setSelection(ranges, true);
-
-		        //resubscribe choiced 
-		        YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
-		    }
-		    
-		    var previousPoint = null;
-
-			function plothover(o) {
-		        var pos = o.pos;
-		        var item = o.item;
-		        
-		        //YUD.get("x").innerHTML = pos.x.toFixed(2);
-		        //YUD.get("y").innerHTML = pos.y.toFixed(2);
-		        if (item) {
-		            if (previousPoint != item.datapoint) {
-		                previousPoint = item.datapoint;
-		                
-		                var tooltip = YUD.get("tooltip");
-		                if(tooltip) {
-		                	  tooltip.parentNode.removeChild(tooltip);
-		                }
-		                var x = item.datapoint.x.toFixed(2);
-		                var y = item.datapoint.y.toFixed(2);
-						
-		                if (!item.series.label){
-		                    item.series.label = 'commits';
-		                }
-		                var d = new Date(x*1000);
-		                var fd = d.toDateString()
-		                var nr_commits = parseInt(y);
-		                
-		                var cur_data = dataset[item.series.label].data[item.dataIndex];
-		                var added = cur_data.added;
-		                var changed = cur_data.changed;
-		                var removed = cur_data.removed;
-		                
-		                var nr_commits_suffix = " ${_('commits')} ";
-		                var added_suffix = " ${_('files added')} ";
-		                var changed_suffix = " ${_('files changed')} ";
-		                var removed_suffix = " ${_('files removed')} ";
-
-		                
-		                if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
-						if(added==1){added_suffix=" ${_('file added')} ";}
-						if(changed==1){changed_suffix=" ${_('file changed')} ";}
-						if(removed==1){removed_suffix=" ${_('file removed')} ";}
-										                
-		                showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
-								 +'<br/>'+
-		                         nr_commits + nr_commits_suffix+'<br/>'+
-		                         added + added_suffix +'<br/>'+
-		                         changed + changed_suffix + '<br/>'+
-		                         removed + removed_suffix + '<br/>');
-		            }
-		        }
-		        else {
-		        	  var tooltip = YUD.get("tooltip");
-		        	  
-			          if(tooltip) {
-			                tooltip.parentNode.removeChild(tooltip);
-			          }
-		            previousPoint = null;
-		        }
-		    }
-			
-		    /**
-		     * MAIN EXECUTION
-		     */
-			
-			var data = getDataAccordingToRanges(initial_ranges);    
-			generateCheckboxes(data);
-			
-		    //main plot 
-		    var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
-		    
-			//overview 
-			var overview = YAHOO.widget.Flot(overviewContainer, 
-					overview_dataset, overview_options);
-			
-			//show initial selection on overview 
-			overview.setSelection(initial_ranges);    
-			
-		    plot.subscribe("plotselected", plotselected);
-		    plot.subscribe("plothover", plothover)
-		    
-		    overview.subscribe("plotselected", function (ranges) {
-		        plot.setSelection(ranges);
-		    });		
-
-		    YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
-		}
-			SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});		
-		</script>
-
+<div class="box">
+    <div class="title">
+        <div class="breadcrumbs">
+        %if c.repo_changesets:
+            ${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}
+        %else:
+            ${_('Quick start')}
+         %endif
+        </div>
     </div>
-</div>    
-
-<div class="box">    
-    <div class="title">
-        <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
-    </div>    
     <div class="table">
         <div id="shortlog_data">
             <%include file='../shortlog/shortlog_data.html'/>
         </div>
-        ##%if c.repo_changesets:
-        ##	${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
-        ##%endif
+    </div>
+</div>
+
+%if c.readme_data:
+<div class="box" style="background-color: #FAFAFA">
+    <div id="readme" class="title">
+        <div class="breadcrumbs"><a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a></div>
+    </div>
+    <div class="readme">
+      <div class="readme_box">
+        ${c.readme_data|n}
+      </div>
     </div>
 </div>
-<div class="box">    
-    <div class="title">
-        <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
-    </div>    
-    <div class="table">
-        <%include file='../tags/tags_data.html'/>
-        %if c.repo_changesets:
-        	${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
-        %endif
-    </div>
-</div>
-<div class="box">
-    <div class="title">
-        <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
-    </div>    
-    <div class="table">
-        <%include file='../branches/branches_data.html'/>
-        %if c.repo_changesets:
-        	${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
-        %endif
-    </div>      
-</div> 
+%endif
+
+<script type="text/javascript">
+var clone_url = 'clone_url';
+YUE.on(clone_url,'click',function(e){
+    if(YUD.hasClass(clone_url,'selected')){
+        return
+    }
+    else{
+        YUD.addClass(clone_url,'selected');
+        YUD.get(clone_url).select();
+    }
+})
+
+YUE.on('clone_by_name','click',function(e){
+    // show url by name and hide name button
+    YUD.setStyle('clone_url','display','');
+    YUD.setStyle('clone_by_name','display','none');
+
+    // hide url by id and show name button
+    YUD.setStyle('clone_by_id','display','');
+    YUD.setStyle('clone_url_id','display','none');
+
+})
+YUE.on('clone_by_id','click',function(e){
+
+	// show url by id and hide id button
+	YUD.setStyle('clone_by_id','display','none');
+    YUD.setStyle('clone_url_id','display','');
+
+    // hide url by name and show id button
+	YUD.setStyle('clone_by_name','display','');
+	YUD.setStyle('clone_url','display','none');
+})
+
+
+var tmpl_links = {};
+%for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
+  tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
+%endfor
+
+YUE.on(['download_options','archive_subrepos'],'change',function(e){
+   var sm = YUD.get('download_options');
+   var new_cs = sm.options[sm.selectedIndex];
+
+   for(k in tmpl_links){
+       var s = YUD.get(k+'_link');
+       if(s){
+         var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
+         title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
+         title_tmpl = title_tmpl.replace('__CS_EXT__',k);
+
+         var url = tmpl_links[k].replace('__CS__',new_cs.value);
+         var subrepos = YUD.get('archive_subrepos').checked;
+         url = url.replace('__SUB__',subrepos);
+         url = url.replace('__NAME__',title_tmpl);
+         s.innerHTML = url
+       }
+   }
+});
+</script>
+%if c.show_stats:
+<script type="text/javascript">
+var data = ${c.trending_languages|n};
+var total = 0;
+var no_data = true;
+var tbl = document.createElement('table');
+tbl.setAttribute('class','trending_language_tbl');
+var cnt = 0;
+for (var i=0;i<data.length;i++){
+	total+= data[i][1].count;
+}
+for (var i=0;i<data.length;i++){
+    cnt += 1;
+    no_data = false;
+
+    var hide = cnt>2;
+    var tr = document.createElement('tr');
+    if (hide){
+        tr.setAttribute('style','display:none');
+        tr.setAttribute('class','stats_hidden');
+    }
+    var k = data[i][0];
+    var obj = data[i][1];
+    var percentage = Math.round((obj.count/total*100),2);
+
+    var td1 = document.createElement('td');
+    td1.width = 150;
+    var trending_language_label = document.createElement('div');
+    trending_language_label.innerHTML = obj.desc+" ("+k+")";
+    td1.appendChild(trending_language_label);
+
+    var td2 = document.createElement('td');
+    td2.setAttribute('style','padding-right:14px !important');
+    var trending_language = document.createElement('div');
+    var nr_files = obj.count+" ${_('files')}";
+
+    trending_language.title = k+" "+nr_files;
+
+    if (percentage>22){
+        trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
+    }
+    else{
+        trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
+    }
+
+    trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
+    trending_language.style.width=percentage+"%";
+    td2.appendChild(trending_language);
+
+    tr.appendChild(td1);
+    tr.appendChild(td2);
+    tbl.appendChild(tr);
+    if(cnt == 3){
+        var show_more = document.createElement('tr');
+        var td = document.createElement('td');
+        lnk = document.createElement('a');
+
+        lnk.href='#';
+        lnk.innerHTML = "${_('show more')}";
+        lnk.id='code_stats_show_more';
+        td.appendChild(lnk);
+
+        show_more.appendChild(td);
+        show_more.appendChild(document.createElement('td'));
+        tbl.appendChild(show_more);
+    }
+
+}
+
+YUD.get('lang_stats').appendChild(tbl);
+YUE.on('code_stats_show_more','click',function(){
+    l = YUD.getElementsByClassName('stats_hidden')
+    for (e in l){
+        YUD.setStyle(l[e],'display','');
+    };
+    YUD.setStyle(YUD.get('code_stats_show_more'),
+            'display','none');
+});
+</script>
+<script type="text/javascript">
+/**
+ * Plots summary graph
+ *
+ * @class SummaryPlot
+ * @param {from} initial from for detailed graph
+ * @param {to} initial to for detailed graph
+ * @param {dataset}
+ * @param {overview_dataset}
+ */
+function SummaryPlot(from,to,dataset,overview_dataset) {
+    var initial_ranges = {
+        "xaxis":{
+            "from":from,
+            "to":to,
+        },
+    };
+    var dataset = dataset;
+    var overview_dataset = [overview_dataset];
+    var choiceContainer = YUD.get("legend_choices");
+    var choiceContainerTable = YUD.get("legend_choices_tables");
+    var plotContainer = YUD.get('commit_history');
+    var overviewContainer = YUD.get('overview');
+
+    var plot_options = {
+        bars: {show:true,align:'center',lineWidth:4},
+        legend: {show:true, container:"legend_container"},
+        points: {show:true,radius:0,fill:false},
+        yaxis: {tickDecimals:0,},
+        xaxis: {
+            mode: "time",
+            timeformat: "%d/%m",
+            min:from,
+            max:to,
+        },
+        grid: {
+            hoverable: true,
+            clickable: true,
+            autoHighlight:true,
+            color: "#999"
+        },
+        //selection: {mode: "x"}
+    };
+    var overview_options = {
+        legend:{show:false},
+        bars: {show:true,barWidth: 2,},
+        shadowSize: 0,
+        xaxis: {mode: "time", timeformat: "%d/%m/%y",},
+        yaxis: {ticks: 3, min: 0,tickDecimals:0,},
+        grid: {color: "#999",},
+        selection: {mode: "x"}
+    };
+
+    /**
+    *get dummy data needed in few places
+    */
+    function getDummyData(label){
+        return {"label":label,
+         "data":[{"time":0,
+             "commits":0,
+                 "added":0,
+                 "changed":0,
+                 "removed":0,
+            }],
+            "schema":["commits"],
+            "color":'#ffffff',
+        }
+    }
+
+    /**
+     * generate checkboxes accordindly to data
+     * @param keys
+     * @returns
+     */
+    function generateCheckboxes(data) {
+        //append checkboxes
+        var i = 0;
+        choiceContainerTable.innerHTML = '';
+        for(var pos in data) {
+
+            data[pos].color = i;
+            i++;
+            if(data[pos].label != ''){
+                choiceContainerTable.innerHTML +=
+                    '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
+                     <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
+            }
+        }
+    }
 
-</%def>    
+    /**
+     * ToolTip show
+     */
+    function showTooltip(x, y, contents) {
+        var div=document.getElementById('tooltip');
+        if(!div) {
+            div = document.createElement('div');
+            div.id="tooltip";
+            div.style.position="absolute";
+            div.style.border='1px solid #fdd';
+            div.style.padding='2px';
+            div.style.backgroundColor='#fee';
+            document.body.appendChild(div);
+        }
+        YUD.setStyle(div, 'opacity', 0);
+        div.innerHTML = contents;
+        div.style.top=(y + 5) + "px";
+        div.style.left=(x + 5) + "px";
+
+        var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
+        anim.animate();
+    }
+
+    /**
+     * This function will detect if selected period has some changesets
+       for this user if it does this data is then pushed for displaying
+       Additionally it will only display users that are selected by the checkbox
+    */
+    function getDataAccordingToRanges(ranges) {
+
+        var data = [];
+        var new_dataset = {};
+        var keys = [];
+        var max_commits = 0;
+        for(var key in dataset){
+
+            for(var ds in dataset[key].data){
+                commit_data = dataset[key].data[ds];
+                if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
+
+                    if(new_dataset[key] === undefined){
+                        new_dataset[key] = {data:[],schema:["commits"],label:key};
+                    }
+                    new_dataset[key].data.push(commit_data);
+                }
+            }
+            if (new_dataset[key] !== undefined){
+                data.push(new_dataset[key]);
+            }
+        }
+
+        if (data.length > 0){
+            return data;
+        }
+        else{
+            //just return dummy data for graph to plot itself
+            return [getDummyData('')];
+        }
+    }
+
+    /**
+    * redraw using new checkbox data
+    */
+    function plotchoiced(e,args){
+        var cur_data = args[0];
+        var cur_ranges = args[1];
+
+        var new_data = [];
+        var inputs = choiceContainer.getElementsByTagName("input");
+
+        //show only checked labels
+        for(var i=0; i<inputs.length; i++) {
+            var checkbox_key = inputs[i].name;
+
+            if(inputs[i].checked){
+                for(var d in cur_data){
+                    if(cur_data[d].label == checkbox_key){
+                        new_data.push(cur_data[d]);
+                    }
+                }
+            }
+            else{
+                //push dummy data to not hide the label
+                new_data.push(getDummyData(checkbox_key));
+            }
+        }
+
+        var new_options = YAHOO.lang.merge(plot_options, {
+            xaxis: {
+                min: cur_ranges.xaxis.from,
+                max: cur_ranges.xaxis.to,
+                mode:"time",
+                timeformat: "%d/%m",
+            },
+        });
+        if (!new_data){
+            new_data = [[0,1]];
+        }
+        // do the zooming
+       plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
+
+       plot.subscribe("plotselected", plotselected);
+
+       //resubscribe plothover
+       plot.subscribe("plothover", plothover);
+
+       // don't fire event on the overview to prevent eternal loop
+       overview.setSelection(cur_ranges, true);
+
+    }
+
+    /**
+     * plot only selected items from overview
+     * @param ranges
+     * @returns
+     */
+    function plotselected(ranges,cur_data) {
+        //updates the data for new plot
+        var data = getDataAccordingToRanges(ranges);
+        generateCheckboxes(data);
+
+        var new_options = YAHOO.lang.merge(plot_options, {
+            xaxis: {
+                min: ranges.xaxis.from,
+                max: ranges.xaxis.to,
+                mode:"time",
+                timeformat: "%d/%m",
+            },
+        });
+        // do the zooming
+        plot = YAHOO.widget.Flot(plotContainer, data, new_options);
+
+        plot.subscribe("plotselected", plotselected);
+
+        //resubscribe plothover
+        plot.subscribe("plothover", plothover);
+
+        // don't fire event on the overview to prevent eternal loop
+        overview.setSelection(ranges, true);
+
+        //resubscribe choiced
+        YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
+    }
+
+    var previousPoint = null;
+
+    function plothover(o) {
+        var pos = o.pos;
+        var item = o.item;
+
+        //YUD.get("x").innerHTML = pos.x.toFixed(2);
+        //YUD.get("y").innerHTML = pos.y.toFixed(2);
+        if (item) {
+            if (previousPoint != item.datapoint) {
+                previousPoint = item.datapoint;
+
+                var tooltip = YUD.get("tooltip");
+                if(tooltip) {
+                      tooltip.parentNode.removeChild(tooltip);
+                }
+                var x = item.datapoint.x.toFixed(2);
+                var y = item.datapoint.y.toFixed(2);
+
+                if (!item.series.label){
+                    item.series.label = 'commits';
+                }
+                var d = new Date(x*1000);
+                var fd = d.toDateString()
+                var nr_commits = parseInt(y);
+
+                var cur_data = dataset[item.series.label].data[item.dataIndex];
+                var added = cur_data.added;
+                var changed = cur_data.changed;
+                var removed = cur_data.removed;
+
+                var nr_commits_suffix = " ${_('commits')} ";
+                var added_suffix = " ${_('files added')} ";
+                var changed_suffix = " ${_('files changed')} ";
+                var removed_suffix = " ${_('files removed')} ";
+
+
+                if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
+                if(added==1){added_suffix=" ${_('file added')} ";}
+                if(changed==1){changed_suffix=" ${_('file changed')} ";}
+                if(removed==1){removed_suffix=" ${_('file removed')} ";}
+
+                showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
+                         +'<br/>'+
+                         nr_commits + nr_commits_suffix+'<br/>'+
+                         added + added_suffix +'<br/>'+
+                         changed + changed_suffix + '<br/>'+
+                         removed + removed_suffix + '<br/>');
+            }
+        }
+        else {
+              var tooltip = YUD.get("tooltip");
+
+              if(tooltip) {
+                    tooltip.parentNode.removeChild(tooltip);
+              }
+            previousPoint = null;
+        }
+    }
+
+    /**
+     * MAIN EXECUTION
+     */
+
+    var data = getDataAccordingToRanges(initial_ranges);
+    generateCheckboxes(data);
+
+    //main plot
+    var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
+
+    //overview
+    var overview = YAHOO.widget.Flot(overviewContainer,
+            overview_dataset, overview_options);
+
+    //show initial selection on overview
+    overview.setSelection(initial_ranges);
+
+    plot.subscribe("plotselected", plotselected);
+    plot.subscribe("plothover", plothover)
+
+    overview.subscribe("plotselected", function (ranges) {
+        plot.setSelection(ranges);
+    });
+
+    // user choices on overview
+    YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
+}
+    SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
+</script>
+%endif
+
+</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/switch_to_list.html	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,39 @@
+## -*- coding: utf-8 -*-
+<li>
+    ${h.link_to('%s (%s)' % (_('branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
+    <ul>
+    %if c.rhodecode_repo.branches.values():
+        %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
+            <li><div><pre>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</pre></div></li>
+        %endfor
+    %else:
+        <li>${h.link_to(_('There are no branches yet'),'#')}</li>
+    %endif
+    </ul>
+</li>
+<li>
+    ${h.link_to('%s (%s)' % (_('tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
+    <ul>
+    %if c.rhodecode_repo.tags.values():
+        %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
+         <li><div><pre>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</pre></div></li>
+        %endfor
+    %else:
+        <li>${h.link_to(_('There are no tags yet'),'#')}</li>
+    %endif
+    </ul>
+</li>
+%if c.rhodecode_repo.alias == 'hg':
+<li>
+    ${h.link_to('%s (%s)' % (_('bookmarks'),len(c.rhodecode_repo.bookmarks.values()),),h.url('bookmarks_home',repo_name=c.repo_name),class_='bookmarks childs')}
+    <ul>
+    %if c.rhodecode_repo.bookmarks.values():
+        %for cnt,book in enumerate(c.rhodecode_repo.bookmarks.items()):
+         <li><div><pre>${h.link_to('%s - %s' % (book[0],h.short_id(book[1])),h.url('files_home',repo_name=c.repo_name,revision=book[1]))}</pre></div></li>
+        %endfor
+    %else:
+        <li>${h.link_to(_('There are no bookmarks yet'),'#')}</li>
+    %endif
+    </ul>
+</li>
+%endif
--- a/rhodecode/templates/tags/tags.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/tags/tags.html	Sun Feb 26 17:25:09 2012 +0200
@@ -7,8 +7,9 @@
 
 
 <%def name="breadcrumbs_links()">
+    <input class="q_filter_box" id="q_filter_tags" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
     ${h.link_to(u'Home',h.url('/'))}
-    &raquo; 
+    &raquo;
     ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
     &raquo;
     ${_('tags')}
@@ -27,5 +28,49 @@
     <div class="table">
         <%include file='tags_data.html'/>
     </div>
-</div>    
-</%def> 
\ No newline at end of file
+</div>
+<script type="text/javascript">
+
+// main table sorting
+var myColumnDefs = [
+    {key:"name",label:"${_('Name')}",sortable:true},
+    {key:"date",label:"${_('Date')}",sortable:true,
+        sortOptions: { sortFunction: dateSort }},
+    {key:"author",label:"${_('Author')}",sortable:true},
+    {key:"revision",label:"${_('Revision')}",sortable:true,
+        sortOptions: { sortFunction: revisionSort }},
+];
+
+var myDataSource = new YAHOO.util.DataSource(YUD.get("tags_data"));
+
+myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
+
+myDataSource.responseSchema = {
+    fields: [
+        {key:"name"},
+        {key:"date"},
+        {key:"author"},
+        {key:"revision"},
+    ]
+};
+
+var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
+        {
+         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...')}",
+        }
+);
+myDataTable.subscribe('postRenderEvent',function(oArgs) {
+    tooltip_activate();
+    var func = function(node){
+        return node.parentNode.parentNode.parentNode.parentNode.parentNode;
+    }
+    q_filter('q_filter_tags',YUQ('div.table tr td .logtags .tagtag a'),func);
+});
+
+</script>
+</%def>
--- a/rhodecode/templates/tags/tags_data.html	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/templates/tags/tags_data.html	Sun Feb 26 17:25:09 2012 +0200
@@ -1,33 +1,34 @@
-%if c.repo_tags:    
-    <table>
+%if c.repo_tags:
+   <div id="table_wrap" class="yui-skin-sam">
+    <table id="tags_data">
+      <thead>
     	<tr>
-	        <th class="left">${_('date')}</th>
-	        <th class="left">${_('name')}</th>
-	        <th class="left">${_('author')}</th>
-	        <th class="left">${_('revision')}</th>
-			<th class="left">${_('links')}</th>
+            <th class="left">${_('Name')}</th>
+            <th class="left">${_('Date')}</th>
+            <th class="left">${_('Author')}</th>
+            <th class="left">${_('Revision')}</th>
     	</tr>
+      </thead>
 		%for cnt,tag in enumerate(c.repo_tags.items()):
-		<tr class="parity${cnt%2}">		
-	        <td><span class="tooltip" title="${h.age(tag[1].date)}">
-                      ${tag[1].date}</span>
-            </td>
+		<tr class="parity${cnt%2}">
             <td>
                 <span class="logtags">
                     <span class="tagtag">${h.link_to(tag[0],
-                    h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id))}</span>
+                    h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
+                    </span>
                 </span>
-            </td>	        
+            </td>
+            <td><span class="tooltip" title="${h.age(tag[1].date)}">${tag[1].date}</span></td>
 	        <td title="${tag[1].author}">${h.person(tag[1].author)}</td>
-	        <td>r${tag[1].revision}:${h.short_id(tag[1].raw_id)}</td>
-			<td class="nowrap">
-			${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
-			|
-			${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
-			</td>
-		</tr>	
+	        <td>
+                <div>
+                    <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id)}">r${tag[1].revision}:${h.short_id(tag[1].raw_id)}</a></pre>
+                </div>
+            </td>
+		</tr>
 		%endfor
     </table>
+   </div>
 %else:
 	${_('There are no tags yet')}
-%endif    
\ No newline at end of file
+%endif
--- a/rhodecode/tests/__init__.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/__init__.py	Sun Feb 26 17:25:09 2012 +0200
@@ -8,9 +8,12 @@
 setup-app`) and provides the base testing objects.
 """
 import os
+import time
+import logging
 from os.path import join as jn
 
 from unittest import TestCase
+from tempfile import _RandomNameSequence
 
 from paste.deploy import loadapp
 from paste.script.appinstall import SetupCommand
@@ -18,31 +21,47 @@
 from routes.util import URLGenerator
 from webtest import TestApp
 
-from rhodecode.model import meta
-import logging
-
-
-log = logging.getLogger(__name__)
+from rhodecode.model.meta import Session
+from rhodecode.model.db import User
 
 import pylons.test
 
-__all__ = ['environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
-           'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
-           'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS' ]
+os.environ['TZ'] = 'UTC'
+time.tzset()
+
+log = logging.getLogger(__name__)
+
+__all__ = [
+    'environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
+    'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
+    'TEST_USER_ADMIN_LOGIN', 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
+    'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
+    'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL'
+]
 
 # Invoke websetup with the current config file
-#SetupCommand('setup-app').run([config_file])
+# SetupCommand('setup-app').run([config_file])
 
 ##RUNNING DESIRED TESTS
 # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
-# nosetests --pdb --pdb-failures 
+# nosetests --pdb --pdb-failures
 environ = {}
 
 #SOME GLOBALS FOR TESTS
-from tempfile import _RandomNameSequence
+
 TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
 TEST_USER_ADMIN_LOGIN = 'test_admin'
 TEST_USER_ADMIN_PASS = 'test12'
+TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
+
+TEST_USER_REGULAR_LOGIN = 'test_regular'
+TEST_USER_REGULAR_PASS = 'test12'
+TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
+
+TEST_USER_REGULAR2_LOGIN = 'test_regular2'
+TEST_USER_REGULAR2_PASS = 'test12'
+TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
+
 HG_REPO = 'vcs_test_hg'
 GIT_REPO = 'vcs_test_git'
 
@@ -60,12 +79,13 @@
 
         self.app = TestApp(wsgiapp)
         url._push_object(URLGenerator(config['routes.map'], environ))
-        self.sa = meta.Session
+        self.Session = Session
         self.index_location = config['app_conf']['index_dir']
         TestCase.__init__(self, *args, **kwargs)
 
     def log_user(self, username=TEST_USER_ADMIN_LOGIN,
                  password=TEST_USER_ADMIN_PASS):
+        self._logged_username = username
         response = self.app.post(url(controller='login', action='index'),
                                  {'username':username,
                                   'password':password})
@@ -74,10 +94,16 @@
             self.fail('could not login using %s %s' % (username, password))
 
         self.assertEqual(response.status, '302 Found')
-        self.assertEqual(response.session['rhodecode_user'].username, username)
-        return response.follow()
+        ses = response.session['rhodecode_user']
+        self.assertEqual(ses.get('username'), username)
+        response = response.follow()
+        self.assertEqual(ses.get('is_authenticated'), True)
+
+        return response.session['rhodecode_user']
+
+    def _get_logged_user(self):
+        return User.get_by_username(self._logged_username)
 
     def checkSessionFlash(self, response, msg):
         self.assertTrue('flash' in response.session)
         self.assertTrue(msg in response.session['flash'][0][1])
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/_test_concurency.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,211 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.tests.test_hg_operations
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Test suite for making push/pull operations
+
+    :created_on: Dec 30, 2010
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (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 shutil
+import logging
+from os.path import join as jn
+from os.path import dirname as dn
+
+from tempfile import _RandomNameSequence
+from subprocess import Popen, PIPE
+
+from paste.deploy import appconfig
+from pylons import config
+from sqlalchemy import engine_from_config
+
+from rhodecode.lib.utils import add_cache
+from rhodecode.model import init_model
+from rhodecode.model import meta
+from rhodecode.model.db import User, Repository
+from rhodecode.lib.auth import get_crypt_password
+
+from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO
+from rhodecode.config.environment import load_environment
+
+rel_path = dn(dn(dn(os.path.abspath(__file__))))
+conf = appconfig('config:development.ini', relative_to=rel_path)
+load_environment(conf.global_conf, conf.local_conf)
+
+add_cache(conf)
+
+USER = 'test_admin'
+PASS = 'test12'
+HOST = 'hg.local'
+METHOD = 'pull'
+DEBUG = True
+log = logging.getLogger(__name__)
+
+
+class Command(object):
+
+    def __init__(self, cwd):
+        self.cwd = cwd
+
+    def execute(self, cmd, *args):
+        """Runs command on the system with given ``args``.
+        """
+
+        command = cmd + ' ' + ' '.join(args)
+        log.debug('Executing %s' % command)
+        if DEBUG:
+            print command
+        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
+        stdout, stderr = p.communicate()
+        if DEBUG:
+            print stdout, stderr
+        return stdout, stderr
+
+def get_session():
+    engine = engine_from_config(conf, 'sqlalchemy.db1.')
+    init_model(engine)
+    sa = meta.Session
+    return sa
+
+
+def create_test_user(force=True):
+    print 'creating test user'
+    sa = get_session()
+
+    user = sa.query(User).filter(User.username == USER).scalar()
+
+    if force and user is not None:
+        print 'removing current user'
+        for repo in sa.query(Repository).filter(Repository.user == user).all():
+            sa.delete(repo)
+        sa.delete(user)
+        sa.commit()
+
+    if user is None or force:
+        print 'creating new one'
+        new_usr = User()
+        new_usr.username = USER
+        new_usr.password = get_crypt_password(PASS)
+        new_usr.email = 'mail@mail.com'
+        new_usr.name = 'test'
+        new_usr.lastname = 'lasttestname'
+        new_usr.active = True
+        new_usr.admin = True
+        sa.add(new_usr)
+        sa.commit()
+
+    print 'done'
+
+
+def create_test_repo(force=True):
+    print 'creating test repo'
+    from rhodecode.model.repo import RepoModel
+    sa = get_session()
+
+    user = sa.query(User).filter(User.username == USER).scalar()
+    if user is None:
+        raise Exception('user not found')
+
+
+    repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar()
+
+    if repo is None:
+        print 'repo not found creating'
+
+        form_data = {'repo_name':HG_REPO,
+                     'repo_type':'hg',
+                     'private':False,
+                     'clone_uri':'' }
+        rm = RepoModel(sa)
+        rm.base_path = '/home/hg'
+        rm.create(form_data, user)
+
+    print 'done'
+
+def set_anonymous_access(enable=True):
+    sa = get_session()
+    user = sa.query(User).filter(User.username == 'default').one()
+    user.active = enable
+    sa.add(user)
+    sa.commit()
+
+def get_anonymous_access():
+    sa = get_session()
+    return sa.query(User).filter(User.username == 'default').one().active
+
+
+#==============================================================================
+# TESTS
+#==============================================================================
+def test_clone_with_credentials(no_errors=False, repo=HG_REPO, method=METHOD,
+                                seq=None):
+    cwd = path = jn(TESTS_TMP_PATH, repo)
+
+    if seq == None:
+        seq = _RandomNameSequence().next()
+
+    try:
+        shutil.rmtree(path, ignore_errors=True)
+        os.makedirs(path)
+        #print 'made dirs %s' % jn(path)
+    except OSError:
+        raise
+
+    clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
+                  {'user':USER,
+                   'pass':PASS,
+                   'host':HOST,
+                   'cloned_repo':repo, }
+
+    dest = path + seq
+    if method == 'pull':
+        stdout, stderr = Command(cwd).execute('hg', method, '--cwd', dest, clone_url)
+    else:
+        stdout, stderr = Command(cwd).execute('hg', method, clone_url, dest)
+
+        if no_errors is False:
+            assert """adding file changes""" in stdout, 'no messages about cloning'
+            assert """abort""" not in stderr , 'got error from clone'
+
+if __name__ == '__main__':
+    try:
+        create_test_user(force=False)
+        seq = None
+        import time
+
+        try:
+            METHOD = sys.argv[3]
+        except:
+            pass
+
+        if METHOD == 'pull':
+            seq = _RandomNameSequence().next()
+            test_clone_with_credentials(repo=sys.argv[1], method='clone',
+                                        seq=seq)
+        s = time.time()
+        for i in range(1, int(sys.argv[2]) + 1):
+            print 'take', i
+            test_clone_with_credentials(repo=sys.argv[1], method=METHOD,
+                                        seq=seq)
+        print 'time taken %.3f' % (time.time() - s)
+    except Exception, e:
+        raise
+        sys.exit('stop on %s' % e)
--- a/rhodecode/tests/functional/test_admin_ldap_settings.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_admin_ldap_settings.py	Sun Feb 26 17:25:09 2012 +0200
@@ -1,5 +1,5 @@
 from rhodecode.tests import *
-from rhodecode.model.db import RhodeCodeSettings
+from rhodecode.model.db import RhodeCodeSetting
 from nose.plugins.skip import SkipTest
 
 skip_ldap_test = False
@@ -22,7 +22,7 @@
         self.log_user()
         if skip_ldap_test:
             raise SkipTest('skipping due to missing ldap lib')
-        
+
         test_url = url(controller='admin/ldap_settings',
                        action='ldap_settings')
 
@@ -41,7 +41,8 @@
                     'ldap_attr_lastname':'tester',
                     'ldap_attr_email':'test@example.com' })
 
-        new_settings = RhodeCodeSettings.get_ldap_settings()
+        new_settings = RhodeCodeSetting.get_ldap_settings()
+        print new_settings
         self.assertEqual(new_settings['ldap_host'], u'dc.example.com',
                          'fail db write compare')
 
@@ -52,7 +53,7 @@
         self.log_user()
         if skip_ldap_test:
             raise SkipTest('skipping due to missing ldap lib')
-                
+
         test_url = url(controller='admin/ldap_settings',
                        action='ldap_settings')
 
@@ -70,13 +71,13 @@
                     'ldap_attr_firstname':'',
                     'ldap_attr_lastname':'',
                     'ldap_attr_email':'' })
-        
+
         self.assertTrue("""<span class="error-message">The LDAP Login"""
                         """ attribute of the CN must be specified""" in
                         response.body)
-        
-        
-        
+
+
+
         self.assertTrue("""<span class="error-message">Please """
                         """enter a number</span>""" in response.body)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/functional/test_admin_notifications.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,119 @@
+from rhodecode.tests import *
+from rhodecode.model.db import Notification, User, UserNotification
+
+from rhodecode.model.user import UserModel
+from rhodecode.model.notification import NotificationModel
+from rhodecode.model.meta import Session
+
+class TestNotificationsController(TestController):
+
+
+    def tearDown(self):
+        for n in Notification.query().all():
+            inst = Notification.get(n.notification_id)
+            Session.delete(inst)
+        Session.commit()
+
+    def test_index(self):
+        self.log_user()
+
+        u1 = UserModel().create_or_update(username='u1', password='qweqwe',
+                                               email='u1@rhodecode.org',
+                                               name='u1', lastname='u1').user_id
+
+        response = self.app.get(url('notifications'))
+        self.assertTrue('''<div class="table">No notifications here yet</div>'''
+                        in response.body)
+
+        cur_user = self._get_logged_user()
+
+        NotificationModel().create(created_by=u1, subject=u'test_notification_1',
+                                   body=u'notification_1',
+                                   recipients=[cur_user])
+        Session.commit()
+        response = self.app.get(url('notifications'))
+        self.assertTrue(u'test_notification_1' in response.body)
+
+#    def test_index_as_xml(self):
+#        response = self.app.get(url('formatted_notifications', format='xml'))
+#
+#    def test_create(self):
+#        response = self.app.post(url('notifications'))
+#
+#    def test_new(self):
+#        response = self.app.get(url('new_notification'))
+#
+#    def test_new_as_xml(self):
+#        response = self.app.get(url('formatted_new_notification', format='xml'))
+#
+#    def test_update(self):
+#        response = self.app.put(url('notification', notification_id=1))
+#
+#    def test_update_browser_fakeout(self):
+#        response = self.app.post(url('notification', notification_id=1), params=dict(_method='put'))
+
+    def test_delete(self):
+        self.log_user()
+        cur_user = self._get_logged_user()
+
+        u1 = UserModel().create_or_update(username='u1', password='qweqwe',
+                                               email='u1@rhodecode.org',
+                                               name='u1', lastname='u1')
+        u2 = UserModel().create_or_update(username='u2', password='qweqwe',
+                                               email='u2@rhodecode.org',
+                                               name='u2', lastname='u2')
+
+        # make notifications
+        notification = NotificationModel().create(created_by=cur_user,
+                                                  subject=u'test',
+                                                  body=u'hi there',
+                                                  recipients=[cur_user, u1, u2])
+        Session.commit()
+        u1 = User.get(u1.user_id)
+        u2 = User.get(u2.user_id)
+
+        # check DB
+        get_notif = lambda un:[x.notification for x in un]
+        self.assertEqual(get_notif(cur_user.notifications), [notification])
+        self.assertEqual(get_notif(u1.notifications), [notification])
+        self.assertEqual(get_notif(u2.notifications), [notification])
+        cur_usr_id = cur_user.user_id
+
+
+        response = self.app.delete(url('notification',
+                                       notification_id=
+                                       notification.notification_id))
+
+        cur_user = User.get(cur_usr_id)
+        self.assertEqual(cur_user.notifications, [])
+
+
+#    def test_delete_browser_fakeout(self):
+#        response = self.app.post(url('notification', notification_id=1), params=dict(_method='delete'))
+
+    def test_show(self):
+        self.log_user()
+        cur_user = self._get_logged_user()
+        u1 = UserModel().create_or_update(username='u1', password='qweqwe',
+                                               email='u1@rhodecode.org',
+                                               name='u1', lastname='u1')
+        u2 = UserModel().create_or_update(username='u2', password='qweqwe',
+                                               email='u2@rhodecode.org',
+                                               name='u2', lastname='u2')
+
+        notification = NotificationModel().create(created_by=cur_user,
+                                                  subject=u'test',
+                                                  body=u'hi there',
+                                                  recipients=[cur_user, u1, u2])
+
+        response = self.app.get(url('notification',
+                                    notification_id=notification.notification_id))
+
+#    def test_show_as_xml(self):
+#        response = self.app.get(url('formatted_notification', notification_id=1, format='xml'))
+#
+#    def test_edit(self):
+#        response = self.app.get(url('edit_notification', notification_id=1))
+#
+#    def test_edit_as_xml(self):
+#        response = self.app.get(url('formatted_edit_notification', notification_id=1, format='xml'))
--- a/rhodecode/tests/functional/test_admin_repos.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_admin_repos.py	Sun Feb 26 17:25:09 2012 +0200
@@ -1,18 +1,16 @@
 # -*- coding: utf-8 -*-
 
 import os
-import vcs
+from rhodecode.lib import vcs
 
 from rhodecode.model.db import Repository
 from rhodecode.tests import *
 
 class TestAdminReposController(TestController):
 
-
     def __make_repo(self):
         pass
 
-
     def test_index(self):
         self.log_user()
         response = self.app.get(url('repos'))
@@ -32,11 +30,10 @@
                                                 'repo_group':'',
                                                 'description':description,
                                                 'private':private})
-
         self.checkSessionFlash(response, 'created repository %s' % (repo_name))
 
         #test if the repo was created in the database
-        new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
+        new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
                                                     repo_name).one()
 
         self.assertEqual(new_repo.repo_name, repo_name)
@@ -73,7 +70,7 @@
                                'created repository %s' % (repo_name_unicode))
 
         #test if the repo was created in the database
-        new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
+        new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
                                                 repo_name_unicode).one()
 
         self.assertEqual(new_repo.repo_name, repo_name_unicode)
@@ -113,7 +110,7 @@
         assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo'
 
         #test if the fork was created in the database
-        new_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).one()
+        new_repo = self.Session.query(Repository).filter(Repository.repo_name == repo_name).one()
 
         assert new_repo.repo_name == repo_name, 'wrong name of repo name in db'
         assert new_repo.description == description, 'wrong description'
@@ -162,7 +159,7 @@
                         response.session['flash'][0])
 
         #test if the repo was created in the database
-        new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
+        new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
                                                     repo_name).one()
 
         self.assertEqual(new_repo.repo_name, repo_name)
@@ -182,7 +179,7 @@
         response.follow()
 
         #check if repo was deleted from db
-        deleted_repo = self.sa.query(Repository).filter(Repository.repo_name
+        deleted_repo = self.Session.query(Repository).filter(Repository.repo_name
                                                         == repo_name).scalar()
 
         self.assertEqual(deleted_repo, None)
--- a/rhodecode/tests/functional/test_admin_settings.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_admin_settings.py	Sun Feb 26 17:25:09 2012 +0200
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from rhodecode.lib.auth import get_crypt_password, check_password
-from rhodecode.model.db import User, RhodeCodeSettings
+from rhodecode.model.db import User, RhodeCodeSetting
 from rhodecode.tests import *
 
 class TestAdminSettingsController(TestController):
@@ -63,7 +63,7 @@
 
         self.checkSessionFlash(response, 'Updated application settings')
 
-        self.assertEqual(RhodeCodeSettings
+        self.assertEqual(RhodeCodeSetting
                          .get_app_settings()['rhodecode_ga_code'], new_ga_code)
 
         response = response.follow()
@@ -85,7 +85,7 @@
 
         self.assertTrue('Updated application settings' in
                         response.session['flash'][0][1])
-        self.assertEqual(RhodeCodeSettings
+        self.assertEqual(RhodeCodeSetting
                         .get_app_settings()['rhodecode_ga_code'], new_ga_code)
 
         response = response.follow()
@@ -109,7 +109,7 @@
                                                      ))
 
             self.checkSessionFlash(response, 'Updated application settings')
-            self.assertEqual(RhodeCodeSettings
+            self.assertEqual(RhodeCodeSetting
                              .get_app_settings()['rhodecode_title'],
                              new_title.decode('utf-8'))
 
@@ -145,7 +145,7 @@
         response.follow()
 
         assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
-        user = self.sa.query(User).filter(User.username == 'test_admin').one()
+        user = self.Session.query(User).filter(User.username == 'test_admin').one()
         assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email)
         assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name)
         assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname)
@@ -171,7 +171,7 @@
         self.checkSessionFlash(response,
                                'Your account was updated successfully')
 
-        user = self.sa.query(User).filter(User.username == 'test_admin').one()
+        user = self.Session.query(User).filter(User.username == 'test_admin').one()
         assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
 
         assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
--- a/rhodecode/tests/functional/test_admin_users.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_admin_users.py	Sun Feb 26 17:25:09 2012 +0200
@@ -1,11 +1,13 @@
 from rhodecode.tests import *
-from rhodecode.model.db import User
+from rhodecode.model.db import User, Permission
 from rhodecode.lib.auth import check_password
 from sqlalchemy.orm.exc import NoResultFound
+from rhodecode.model.user import UserModel
 
 class TestAdminUsersController(TestController):
 
     def test_index(self):
+        self.log_user()
         response = self.app.get(url('users'))
         # Test response...
 
@@ -21,30 +23,31 @@
         lastname = 'lastname'
         email = 'mail@mail.com'
 
-        response = self.app.post(url('users'), {'username':username,
-                                               'password':password,
-                                               'password_confirmation':password_confirmation,
-                                               'name':name,
-                                               'active':True,
-                                               'lastname':lastname,
-                                               'email':email})
+        response = self.app.post(url('users'),
+                                 {'username':username,
+                                   'password':password,
+                                   'password_confirmation':password_confirmation,
+                                   'name':name,
+                                   'active':True,
+                                   'lastname':lastname,
+                                   'email':email})
 
 
-        assert '''created user %s''' % (username) in response.session['flash'][0], 'No flash message about new user'
+        self.assertTrue('''created user %s''' % (username) in
+                        response.session['flash'][0])
 
-        new_user = self.sa.query(User).filter(User.username == username).one()
-
+        new_user = self.Session.query(User).\
+            filter(User.username == username).one()
 
-        assert new_user.username == username, 'wrong info about username'
-        assert check_password(password, new_user.password) == True , 'wrong info about password'
-        assert new_user.name == name, 'wrong info about name'
-        assert new_user.lastname == lastname, 'wrong info about lastname'
-        assert new_user.email == email, 'wrong info about email'
-
+        self.assertEqual(new_user.username,username)
+        self.assertEqual(check_password(password, new_user.password),True)
+        self.assertEqual(new_user.name,name)
+        self.assertEqual(new_user.lastname,lastname)
+        self.assertEqual(new_user.email,email)
 
         response.follow()
         response = response.follow()
-        assert """edit">newtestuser</a>""" in response.body
+        self.assertTrue("""edit">newtestuser</a>""" in response.body)
 
     def test_create_err(self):
         self.log_user()
@@ -61,16 +64,17 @@
                                                'lastname':lastname,
                                                'email':email})
 
-        assert """<span class="error-message">Invalid username</span>""" in response.body
-        assert """<span class="error-message">Please enter a value</span>""" in response.body
-        assert """<span class="error-message">An email address must contain a single @</span>""" in response.body
+        self.assertTrue("""<span class="error-message">Invalid username</span>""" in response.body)
+        self.assertTrue("""<span class="error-message">Please enter a value</span>""" in response.body)
+        self.assertTrue("""<span class="error-message">An email address must contain a single @</span>""" in response.body)
 
         def get_user():
-            self.sa.query(User).filter(User.username == username).one()
+            self.Session.query(User).filter(User.username == username).one()
 
         self.assertRaises(NoResultFound, get_user), 'found user in database'
 
     def test_new(self):
+        self.log_user()
         response = self.app.get(url('new_user'))
 
     def test_new_as_xml(self):
@@ -100,14 +104,17 @@
 
         response = response.follow()
 
-        new_user = self.sa.query(User).filter(User.username == username).one()
+        new_user = self.Session.query(User)\
+            .filter(User.username == username).one()
         response = self.app.delete(url('user', id=new_user.user_id))
 
-        assert """successfully deleted user""" in response.session['flash'][0], 'No info about user deletion'
+        self.assertTrue("""successfully deleted user""" in
+                        response.session['flash'][0])
 
 
     def test_delete_browser_fakeout(self):
-        response = self.app.post(url('user', id=1), params=dict(_method='delete'))
+        response = self.app.post(url('user', id=1),
+                                 params=dict(_method='delete'))
 
     def test_show(self):
         response = self.app.get(url('user', id=1))
@@ -116,7 +123,57 @@
         response = self.app.get(url('formatted_user', id=1, format='xml'))
 
     def test_edit(self):
-        response = self.app.get(url('edit_user', id=1))
+        self.log_user()
+        user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+        response = self.app.get(url('edit_user', id=user.user_id))
+
+
+    def test_add_perm_create_repo(self):
+        self.log_user()
+        perm_none = Permission.get_by_key('hg.create.none')
+        perm_create = Permission.get_by_key('hg.create.repository')
+
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+
+
+        #User should have None permission on creation repository
+        self.assertEqual(UserModel().has_perm(user, perm_none), False)
+        self.assertEqual(UserModel().has_perm(user, perm_create), False)
+
+        response = self.app.post(url('user_perm', id=user.user_id),
+                                 params=dict(_method='put',
+                                             create_repo_perm=True))
+
+        perm_none = Permission.get_by_key('hg.create.none')
+        perm_create = Permission.get_by_key('hg.create.repository')
+
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        #User should have None permission on creation repository
+        self.assertEqual(UserModel().has_perm(user, perm_none), False)
+        self.assertEqual(UserModel().has_perm(user, perm_create), True)
+
+    def test_revoke_perm_create_repo(self):
+        self.log_user()
+        perm_none = Permission.get_by_key('hg.create.none')
+        perm_create = Permission.get_by_key('hg.create.repository')
+
+        user = User.get_by_username(TEST_USER_REGULAR2_LOGIN)
+
+
+        #User should have None permission on creation repository
+        self.assertEqual(UserModel().has_perm(user, perm_none), False)
+        self.assertEqual(UserModel().has_perm(user, perm_create), False)
+
+        response = self.app.post(url('user_perm', id=user.user_id),
+                                 params=dict(_method='put'))
+
+        perm_none = Permission.get_by_key('hg.create.none')
+        perm_create = Permission.get_by_key('hg.create.repository')
+
+        user = User.get_by_username(TEST_USER_REGULAR2_LOGIN)
+        #User should have None permission on creation repository
+        self.assertEqual(UserModel().has_perm(user, perm_none), True)
+        self.assertEqual(UserModel().has_perm(user, perm_create), False)
 
     def test_edit_as_xml(self):
         response = self.app.get(url('formatted_edit_user', id=1, format='xml'))
--- a/rhodecode/tests/functional/test_admin_users_groups.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_admin_users_groups.py	Sun Feb 26 17:25:09 2012 +0200
@@ -23,10 +23,6 @@
         self.checkSessionFlash(response,
                                'created users group %s' % TEST_USERS_GROUP)
 
-
-
-
-
     def test_new(self):
         response = self.app.get(url('new_users_group'))
 
@@ -52,13 +48,13 @@
                                'created users group %s' % users_group_name)
 
 
-        gr = self.sa.query(UsersGroup)\
+        gr = self.Session.query(UsersGroup)\
                            .filter(UsersGroup.users_group_name ==
                                    users_group_name).one()
 
         response = self.app.delete(url('users_group', id=gr.users_group_id))
 
-        gr = self.sa.query(UsersGroup)\
+        gr = self.Session.query(UsersGroup)\
                            .filter(UsersGroup.users_group_name ==
                                    users_group_name).scalar()
 
@@ -89,7 +85,3 @@
 
     def test_revoke_members(self):
         pass
-
-
-
-
--- a/rhodecode/tests/functional/test_branches.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_branches.py	Sun Feb 26 17:25:09 2012 +0200
@@ -4,15 +4,8 @@
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url(controller='branches', action='index', repo_name=HG_REPO))
-
-        assert """<a href="/%s/changeset/27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</a>""" % HG_REPO in response.body, 'wrong info about default branch'
-        assert """<a href="/%s/changeset/97e8b885c04894463c51898e14387d80c30ed1ee">git</a>""" % HG_REPO in response.body, 'wrong info about default git'
-        assert """<a href="/%s/changeset/2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</a>""" % HG_REPO in response.body, 'wrong info about default web'
-
-
-
-
-
-
-        # Test response...
+        response = self.app.get(url(controller='branches',
+                                    action='index', repo_name=HG_REPO))
+        response.mustcontain("""<a href="/%s/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/">default</a>""" % HG_REPO)
+        response.mustcontain("""<a href="/%s/files/97e8b885c04894463c51898e14387d80c30ed1ee/">git</a>""" % HG_REPO)
+        response.mustcontain("""<a href="/%s/files/2e6a2bf9356ca56df08807f4ad86d480da72a8f4/">web</a>""" % HG_REPO)
--- a/rhodecode/tests/functional/test_changelog.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_changelog.py	Sun Feb 26 17:25:09 2012 +0200
@@ -1,5 +1,6 @@
 from rhodecode.tests import *
 
+
 class TestChangelogController(TestController):
 
     def test_index_hg(self):
@@ -7,23 +8,26 @@
         response = self.app.get(url(controller='changelog', action='index',
                                     repo_name=HG_REPO))
 
-        self.assertTrue("""<div id="chg_20" class="container">"""
-                        in response.body)
-        self.assertTrue("""<input class="changeset_range" id="5e204e7583b9" """
-                        """name="5e204e7583b9" type="checkbox" value="1" />"""
-                        in response.body)
-        self.assertTrue("""<span>commit 154: 5e204e7583b9@2010-08-10 """
-                        """02:18:46</span>""" in response.body)
-        self.assertTrue("""Small update at simplevcs app""" in response.body)
+        response.mustcontain("""<div id="chg_20" class="container tablerow1">""")
+        response.mustcontain(
+            """<input class="changeset_range" id="5e204e7583b9" """
+            """name="5e204e7583b9" type="checkbox" value="1" />"""
+        )
+        response.mustcontain(
+            """<span class="changeset_id">154:"""
+            """<span class="changeset_hash">5e204e7583b9</span></span>"""
+        )
 
+        response.mustcontain("""Small update at simplevcs app""")
 
-        self.assertTrue("""<span id="5e204e7583b9c8e7b93a020bd036564b1e"""
-                        """731dae" class="changed_total tooltip" """
-                        """title="Affected number of files, click to """
-                        """show more details">3</span>""" in response.body)
+        response.mustcontain(
+            """<div id="5e204e7583b9c8e7b93a020bd036564b1e731dae"  """
+            """style="float:right;" class="changed_total tooltip" """
+            """title="Affected number of files, click to show """
+            """more details">3</div>"""
+        )
 
         #pagination
-
         response = self.app.get(url(controller='changelog', action='index',
                                     repo_name=HG_REPO), {'page':1})
         response = self.app.get(url(controller='changelog', action='index',
@@ -37,25 +41,26 @@
         response = self.app.get(url(controller='changelog', action='index',
                                     repo_name=HG_REPO), {'page':6})
 
-
         # Test response after pagination...
-        self.assertTrue("""<input class="changeset_range" id="46ad32a4f974" """
-                        """name="46ad32a4f974" type="checkbox" value="1" />"""
-                        in response.body)
-        self.assertTrue("""<span>commit 64: 46ad32a4f974@2010-04-20"""
-                        """ 01:33:21</span>"""in response.body)
+        response.mustcontain(
+            """<input class="changeset_range" id="46ad32a4f974" """
+            """name="46ad32a4f974" type="checkbox" value="1" />"""
+        )
+        response.mustcontain(
+            """<span class="changeset_id">64:"""
+            """<span class="changeset_hash">46ad32a4f974</span></span>"""
+        )
 
-        self.assertTrue("""<span id="46ad32a4f974e45472a898c6b0acb600320"""
-                        """579b1" class="changed_total tooltip" """
-                        """title="Affected number of files, click to """
-                        """show more details">21</span>"""in response.body)
-        self.assertTrue("""<div class="message"><a href="/%s/changeset/"""
-                        """46ad32a4f974e45472a898c6b0acb600320579b1">"""
-                        """Merge with 2e6a2bf9356ca56df08807f4ad86d48"""
-                        """0da72a8f4</a></div>""" % HG_REPO in response.body)
+        response.mustcontain(
+            """<div id="46ad32a4f974e45472a898c6b0acb600320579b1"  """
+            """style="float:right;" class="changed_total tooltip" """
+            """title="Affected number of files, click to show """
+            """more details">21</div>"""
+        )
 
-
-
-    #def test_index_git(self):
-    #    self.log_user()
-    #    response = self.app.get(url(controller='changelog', action='index', repo_name=GIT_REPO))
+        response.mustcontain(
+            """<a href="/%s/changeset/"""
+            """46ad32a4f974e45472a898c6b0acb600320579b1" """
+            """title="Merge with 2e6a2bf9356ca56df08807f4ad86d480da72a8f4">"""
+            """46ad32a4f974</a>""" % HG_REPO
+        )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/functional/test_changeset_comments.py	Sun Feb 26 17:25:09 2012 +0200
@@ -0,0 +1,139 @@
+from rhodecode.tests import *
+from rhodecode.model.db import ChangesetComment, Notification, User, \
+    UserNotification
+
+class TestChangeSetCommentrController(TestController):
+
+    def setUp(self):
+        for x in ChangesetComment.query().all():
+            self.Session.delete(x)
+        self.Session.commit()
+
+        for x in Notification.query().all():
+            self.Session.delete(x)
+        self.Session.commit()
+
+    def tearDown(self):
+        for x in ChangesetComment.query().all():
+            self.Session.delete(x)
+        self.Session.commit()
+
+        for x in Notification.query().all():
+            self.Session.delete(x)
+        self.Session.commit()
+
+    def test_create(self):
+        self.log_user()
+        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
+        text = u'CommentOnRevision'
+
+        params = {'text':text}
+        response = self.app.post(url(controller='changeset', action='comment',
+                                     repo_name=HG_REPO, revision=rev),
+                                     params=params)
+        # Test response...
+        self.assertEqual(response.status, '302 Found')
+        response.follow()
+
+        response = self.app.get(url(controller='changeset', action='index',
+                                repo_name=HG_REPO, revision=rev))
+        # test DB
+        self.assertEqual(ChangesetComment.query().count(), 1)
+        self.assertTrue('''<div class="comments-number">%s '''
+                        '''comment(s) (0 inline)</div>''' % 1 in response.body)
+
+
+        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)
+
+    def test_create_inline(self):
+        self.log_user()
+        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
+        text = u'CommentOnRevision'
+        f_path = 'vcs/web/simplevcs/views/repository.py'
+        line = 'n1'
+
+        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)
+        # Test response...
+        self.assertEqual(response.status, '302 Found')
+        response.follow()
+
+        response = self.app.get(url(controller='changeset', action='index',
+                                repo_name=HG_REPO, revision=rev))
+        #test DB
+        self.assertEqual(ChangesetComment.query().count(), 1)
+        self.assertTrue('''<div class="comments-number">0 comment(s)'''
+                        ''' (%s inline)</div>''' % 1 in response.body)
+        self.assertTrue('''<div class="inline-comment-placeholder-line"'''
+                        ''' line="n1" target_id="vcswebsimplevcsviews'''
+                        '''repositorypy">''' in response.body)
+
+        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)
+
+    def test_create_with_mention(self):
+        self.log_user()
+
+        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
+        text = u'@test_regular check CommentOnRevision'
+
+        params = {'text':text}
+        response = self.app.post(url(controller='changeset', action='comment',
+                                     repo_name=HG_REPO, revision=rev),
+                                     params=params)
+        # Test response...
+        self.assertEqual(response.status, '302 Found')
+        response.follow()
+
+        response = self.app.get(url(controller='changeset', action='index',
+                                repo_name=HG_REPO, revision=rev))
+        # test DB
+        self.assertEqual(ChangesetComment.query().count(), 1)
+        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()]
+
+        # test_regular get's notification by @mention
+        self.assertEqual(sorted(users), [u'test_admin', u'test_regular'])
+
+    def test_delete(self):
+        self.log_user()
+        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
+        text = u'CommentOnRevision'
+
+        params = {'text':text}
+        response = self.app.post(url(controller='changeset', action='comment',
+                                     repo_name=HG_REPO, revision=rev),
+                                     params=params)
+
+        comments = ChangesetComment.query().all()
+        self.assertEqual(len(comments), 1)
+        comment_id = comments[0].comment_id
+
+
+        self.app.delete(url(controller='changeset',
+                                    action='delete_comment',
+                                    repo_name=HG_REPO,
+                                    comment_id=comment_id))
+
+        comments = ChangesetComment.query().all()
+        self.assertEqual(len(comments), 0)
+
+        response = self.app.get(url(controller='changeset', action='index',
+                                repo_name=HG_REPO, revision=rev))
+        self.assertTrue('''<div class="comments-number">0 comment(s)'''
+                        ''' (0 inline)</div>''' in response.body)
--- a/rhodecode/tests/functional/test_files.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_files.py	Sun Feb 26 17:25:09 2012 +0200
@@ -6,6 +6,7 @@
     '.zip': ('application/zip', 'zip', ''),
 }
 
+
 class TestFilesController(TestController):
 
     def test_index(self):
@@ -15,32 +16,29 @@
                                     revision='tip',
                                     f_path='/'))
         # Test response...
-        assert '<a class="browser-dir" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/docs">docs</a>' in response.body, 'missing dir'
-        assert '<a class="browser-dir" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/tests">tests</a>' in response.body, 'missing dir'
-        assert '<a class="browser-dir" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/vcs">vcs</a>' in response.body, 'missing dir'
-        assert '<a class="browser-file" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/.hgignore">.hgignore</a>' in response.body, 'missing file'
-        assert '<a class="browser-file" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/MANIFEST.in">MANIFEST.in</a>' in response.body, 'missing file'
-
+        response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/docs">docs</a>')
+        response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/tests">tests</a>')
+        response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/vcs">vcs</a>')
+        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/.hgignore">.hgignore</a>')
+        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/MANIFEST.in">MANIFEST.in</a>')
 
     def test_index_revision(self):
         self.log_user()
 
-        response = self.app.get(url(controller='files', action='index',
-                                    repo_name=HG_REPO,
-                                    revision='7ba66bec8d6dbba14a2155be32408c435c5f4492',
-                                    f_path='/'))
-
-
+        response = self.app.get(
+            url(controller='files', action='index',
+                repo_name=HG_REPO,
+                revision='7ba66bec8d6dbba14a2155be32408c435c5f4492',
+                f_path='/')
+        )
 
         #Test response...
 
-        assert '<a class="browser-dir" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs">docs</a>' in response.body, 'missing dir'
-        assert '<a class="browser-dir" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests">tests</a>' in response.body, 'missing dir'
-        assert '<a class="browser-file" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst">README.rst</a>' in response.body, 'missing file'
-        assert '1.1 KiB' in response.body, 'missing size of setup.py'
-        assert 'text/x-python' in response.body, 'missing mimetype of setup.py'
-
-
+        response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs">docs</a>')
+        response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests">tests</a>')
+        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst">README.rst</a>')
+        response.mustcontain('1.1 KiB')
+        response.mustcontain('text/x-python')
 
     def test_index_different_branch(self):
         self.log_user()
@@ -50,11 +48,7 @@
                                     revision='97e8b885c04894463c51898e14387d80c30ed1ee',
                                     f_path='/'))
 
-
-
-        assert """<span style="text-transform: uppercase;"><a href="#">branch: git</a></span>""" in response.body, 'missing or wrong branch info'
-
-
+        response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: git</a></span>""")
 
     def test_index_paging(self):
         self.log_user()
@@ -70,7 +64,7 @@
                                     revision=r[1],
                                     f_path='/'))
 
-            assert """@ r%s:%s""" % (r[0], r[1][:12]) in response.body, 'missing info about current revision'
+            response.mustcontain("""@ r%s:%s""" % (r[0], r[1][:12]))
 
     def test_file_source(self):
         self.log_user()
@@ -80,40 +74,40 @@
                                     f_path='vcs/nodes.py'))
 
         #test or history
-        assert """<optgroup label="Changesets">
-<option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776</option>
-<option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35</option>
-<option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c</option>
-<option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329</option>
-<option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf</option>
-<option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81</option>
-<option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc</option>
-<option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21</option>
-<option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2</option>
-<option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85</option>
-<option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2</option>
-<option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93</option>
-<option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b</option>
-<option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455</option>
-<option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32</option>
-<option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0</option>
-<option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081</option>
-<option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad</option>
-<option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7</option>
-<option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059</option>
-<option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb</option>
-<option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1</option>
-<option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71</option>
-<option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771</option>
-<option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526</option>
-<option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4</option>
-<option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b</option>
-<option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c</option>
-<option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1</option>
-<option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5</option>
-<option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283</option>
-<option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6</option>
-<option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3</option>
+        response.mustcontain("""<optgroup label="Changesets">
+<option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
+<option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
+<option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
+<option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
+<option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
+<option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
+<option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
+<option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
+<option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
+<option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
+<option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
+<option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
+<option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
+<option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
+<option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
+<option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
+<option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
+<option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
+<option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
+<option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
+<option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
+<option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
+<option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
+<option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
+<option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
+<option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
+<option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
+<option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
+<option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
+<option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
+<option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
+<option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
+<option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
 </optgroup>
 <optgroup label="Branches">
 <option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
@@ -126,16 +120,15 @@
 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
-</optgroup>""" in response.body
+</optgroup>""")
 
-
-        assert """<div class="commit">"Partially implemented #16. filecontent/commit message/author/node name are safe_unicode now.
+        response.mustcontain("""<div class="commit">Partially implemented #16. filecontent/commit message/author/node name are safe_unicode now.
 In addition some other __str__ are unicode as well
 Added test for unicode
 Improved test to clone into uniq repository.
-removed extra unicode conversion in diff."</div>""" in response.body
+removed extra unicode conversion in diff.</div>""")
 
-        assert """<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""" in response.body, 'missing or wrong branch info'
+        response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
 
     def test_file_annotation(self):
         self.log_user()
@@ -144,41 +137,41 @@
                                     revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
                                     f_path='vcs/nodes.py'))
 
-        print response.body
-        assert """<optgroup label="Changesets">
-<option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776</option>
-<option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35</option>
-<option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c</option>
-<option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329</option>
-<option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf</option>
-<option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81</option>
-<option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc</option>
-<option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21</option>
-<option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2</option>
-<option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85</option>
-<option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2</option>
-<option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93</option>
-<option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b</option>
-<option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455</option>
-<option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32</option>
-<option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0</option>
-<option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081</option>
-<option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad</option>
-<option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7</option>
-<option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059</option>
-<option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb</option>
-<option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1</option>
-<option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71</option>
-<option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771</option>
-<option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526</option>
-<option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4</option>
-<option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b</option>
-<option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c</option>
-<option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1</option>
-<option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5</option>
-<option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283</option>
-<option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6</option>
-<option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3</option>
+
+        response.mustcontain("""<optgroup label="Changesets">
+<option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
+<option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
+<option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
+<option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
+<option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
+<option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
+<option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
+<option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
+<option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
+<option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
+<option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
+<option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
+<option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
+<option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
+<option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
+<option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
+<option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
+<option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
+<option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
+<option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
+<option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
+<option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
+<option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
+<option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
+<option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
+<option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
+<option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
+<option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
+<option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
+<option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
+<option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
+<option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
+<option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
 </optgroup>
 <optgroup label="Branches">
 <option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
@@ -191,11 +184,10 @@
 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
-</optgroup>""" in response.body, 'missing or wrong history in annotation'
+</optgroup>
+""")
 
-        assert """<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""" in response.body, 'missing or wrong branch info'
-
-
+        response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
 
     def test_archival(self):
         self.log_user()
@@ -260,10 +252,11 @@
                                     revision=rev,
                                     f_path=f_path))
 
-        assert """Revision %r does not exist for this repository""" % (rev) in response.session['flash'][0][1], 'No flash message'
-        assert """%s""" % (HG_REPO) in response.session['flash'][0][1], 'No flash message'
+        msg = """Revision %r does not exist for this repository""" % (rev)
+        self.checkSessionFlash(response, msg)
 
-
+        msg = """%s""" % (HG_REPO)
+        self.checkSessionFlash(response, msg)
 
     def test_raw_file_wrong_f_path(self):
         self.log_user()
@@ -273,7 +266,9 @@
                                     repo_name=HG_REPO,
                                     revision=rev,
                                     f_path=f_path))
-        assert "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12]) in response.session['flash'][0][1], 'No flash message'
+
+        msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
+        self.checkSessionFlash(response, msg)
 
     #==========================================================================
     # RAW RESPONSE - PLAIN
@@ -296,10 +291,11 @@
                                     repo_name=HG_REPO,
                                     revision=rev,
                                     f_path=f_path))
+        msg = """Revision %r does not exist for this repository""" % (rev)
+        self.checkSessionFlash(response, msg)
 
-        assert """Revision %r does not exist for this repository""" % (rev) in response.session['flash'][0][1], 'No flash message'
-        assert """%s""" % (HG_REPO) in response.session['flash'][0][1], 'No flash message'
-
+        msg = """%s""" % (HG_REPO)
+        self.checkSessionFlash(response, msg)
 
     def test_raw_wrong_f_path(self):
         self.log_user()
@@ -309,5 +305,14 @@
                                     repo_name=HG_REPO,
                                     revision=rev,
                                     f_path=f_path))
+        msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
+        self.checkSessionFlash(response, msg)
 
-        assert "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12]) in response.session['flash'][0][1], 'No flash message'
+    def test_ajaxed_files_list(self):
+        self.log_user()
+        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
+        response = self.app.get(
+            url('files_nodelist_home', repo_name=HG_REPO,f_path='/',revision=rev),
+            extra_environ={'HTTP_X_PARTIAL_XHR': '1'},
+        )
+        response.mustcontain("vcs/web/simplevcs/views/repository.py")
--- a/rhodecode/tests/functional/test_forks.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_forks.py	Sun Feb 26 17:25:09 2012 +0200
@@ -20,10 +20,13 @@
         fork_name = HG_FORK
         description = 'fork of vcs test'
         repo_name = HG_REPO
-        response = self.app.post(url(controller='settings',
+        org_repo = Repository.get_by_repo_name(repo_name)
+        response = self.app.post(url(controller='forks',
                                      action='fork_create',
                                     repo_name=repo_name),
-                                    {'fork_name':fork_name,
+                                    {'repo_name':fork_name,
+                                     'repo_group':'',
+                                     'fork_parent_id':org_repo.repo_id,
                                      'repo_type':'hg',
                                      'description':description,
                                      'private':'False'})
@@ -39,3 +42,45 @@
         #remove this fork
         response = self.app.delete(url('repo', repo_name=fork_name))
 
+
+
+
+    def test_z_fork_create(self):
+        self.log_user()
+        fork_name = HG_FORK
+        description = 'fork of vcs test'
+        repo_name = HG_REPO
+        org_repo = Repository.get_by_repo_name(repo_name)
+        response = self.app.post(url(controller='forks', action='fork_create',
+                                    repo_name=repo_name),
+                                    {'repo_name':fork_name,
+                                     'repo_group':'',
+                                     'fork_parent_id':org_repo.repo_id,
+                                     'repo_type':'hg',
+                                     'description':description,
+                                     'private':'False'})
+
+        #test if we have a message that fork is ok
+        self.assertTrue('forked %s repository as %s' \
+                      % (repo_name, fork_name) in response.session['flash'][0])
+
+        #test if the fork was created in the database
+        fork_repo = self.Session.query(Repository)\
+            .filter(Repository.repo_name == fork_name).one()
+
+        self.assertEqual(fork_repo.repo_name, fork_name)
+        self.assertEqual(fork_repo.fork.repo_name, repo_name)
+
+
+        #test if fork is visible in the list ?
+        response = response.follow()
+
+
+        # check if fork is marked as fork
+        # wait for cache to expire
+        import time
+        time.sleep(10)
+        response = self.app.get(url(controller='summary', action='index',
+                                    repo_name=fork_name))
+
+        self.assertTrue('Fork of %s' % repo_name in response.body)
--- a/rhodecode/tests/functional/test_home.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_home.py	Sun Feb 26 17:25:09 2012 +0200
@@ -1,22 +1,22 @@
 from rhodecode.tests import *
 
+
 class TestHomeController(TestController):
 
     def test_index(self):
         self.log_user()
         response = self.app.get(url(controller='home', action='index'))
         #if global permission is set
-        self.assertTrue('ADD NEW REPOSITORY' in response.body)
-        self.assertTrue('href="/%s/summary"' % HG_REPO in response.body)
-        # Test response...
+        response.mustcontain('ADD REPOSITORY')
+        response.mustcontain('href="/%s/summary"' % HG_REPO)
 
-        self.assertTrue("""<img class="icon" title="Mercurial repository" """
+        response.mustcontain("""<img class="icon" title="Mercurial repository" """
                         """alt="Mercurial repository" src="/images/icons/hg"""
-                        """icon.png"/>""" in response.body)
-        self.assertTrue("""<img class="icon" title="public repository" """
+                        """icon.png"/>""")
+        response.mustcontain("""<img class="icon" title="public repository" """
                         """alt="public repository" src="/images/icons/lock_"""
-                        """open.png"/>""" in response.body)
-        
-        self.assertTrue("""<a title="Marcin Kuzminski &lt;marcin@python-works.com&gt;
-merge" class="tooltip" href="/vcs_test_hg/changeset/27cd5cce30c96924232dffcd24178a07ffeb5dfc">r173:27cd5cce30c9</a>"""
-                            in response.body)
+                        """open.png"/>""")
+
+        response.mustcontain(
+"""<a title="Marcin Kuzminski &lt;marcin@python-works.com&gt;:\n
+merge" class="tooltip" href="/vcs_test_hg/changeset/27cd5cce30c96924232dffcd24178a07ffeb5dfc">r173:27cd5cce30c9</a>""")
--- a/rhodecode/tests/functional/test_journal.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_journal.py	Sun Feb 26 17:25:09 2012 +0200
@@ -16,10 +16,10 @@
 
     def test_stop_following_repository(self):
         session = self.log_user()
-#        usr = self.sa.query(User).filter(User.username == 'test_admin').one()
-#        repo = self.sa.query(Repository).filter(Repository.repo_name == HG_REPO).one()
+#        usr = self.Session.query(User).filter(User.username == 'test_admin').one()
+#        repo = self.Session.query(Repository).filter(Repository.repo_name == HG_REPO).one()
 #
-#        followings = self.sa.query(UserFollowing)\
+#        followings = self.Session.query(UserFollowing)\
 #            .filter(UserFollowing.user == usr)\
 #            .filter(UserFollowing.follows_repository == repo).all()
 #
--- a/rhodecode/tests/functional/test_login.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_login.py	Sun Feb 26 17:25:09 2012 +0200
@@ -1,12 +1,19 @@
 # -*- coding: utf-8 -*-
 from rhodecode.tests import *
-from rhodecode.model.db import User
+from rhodecode.model.db import User, Notification
 from rhodecode.lib import generate_api_key
 from rhodecode.lib.auth import check_password
-
+from rhodecode.model.meta import Session
 
 class TestLoginController(TestController):
 
+    def tearDown(self):
+        for n in Notification.query().all():
+            Session.delete(n)
+
+        Session.commit()
+        self.assertEqual(Notification.query().all(), [])
+
     def test_index(self):
         response = self.app.get(url(controller='login', action='index'))
         self.assertEqual(response.status, '200 OK')
@@ -17,7 +24,7 @@
                                  {'username':'test_admin',
                                   'password':'test12'})
         self.assertEqual(response.status, '302 Found')
-        self.assertEqual(response.session['rhodecode_user'].username ,
+        self.assertEqual(response.session['rhodecode_user'].get('username') ,
                          'test_admin')
         response = response.follow()
         self.assertTrue('%s repository' % HG_REPO in response.body)
@@ -28,7 +35,7 @@
                                   'password':'test12'})
 
         self.assertEqual(response.status, '302 Found')
-        self.assertEqual(response.session['rhodecode_user'].username ,
+        self.assertEqual(response.session['rhodecode_user'].get('username') ,
                          'test_regular')
         response = response.follow()
         self.assertTrue('%s repository' % HG_REPO in response.body)
@@ -192,7 +199,7 @@
         self.assertEqual(response.status , '302 Found')
         assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
 
-        ret = self.sa.query(User).filter(User.username == 'test_regular4').one()
+        ret = self.Session.query(User).filter(User.username == 'test_regular4').one()
         assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
         assert check_password(password, ret.password) == True , 'password mismatch'
         assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
@@ -224,8 +231,8 @@
         new.name = name
         new.lastname = lastname
         new.api_key = generate_api_key(username)
-        self.sa.add(new)
-        self.sa.commit()
+        self.Session.add(new)
+        self.Session.commit()
 
         response = self.app.post(url(controller='login',
                                      action='password_reset'),
@@ -247,7 +254,6 @@
         # GOOD KEY
 
         key = User.get_by_username(username).api_key
-
         response = self.app.get(url(controller='login',
                                     action='password_reset_confirmation',
                                     key=key))
--- a/rhodecode/tests/functional/test_settings.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_settings.py	Sun Feb 26 17:25:09 2012 +0200
@@ -8,42 +8,3 @@
         response = self.app.get(url(controller='settings', action='index',
                                     repo_name=HG_REPO))
         # Test response...
-
-    def test_fork(self):
-        self.log_user()
-        response = self.app.get(url(controller='settings', action='fork',
-                                    repo_name=HG_REPO))
-
-
-    def test_fork_create(self):
-        self.log_user()
-        fork_name = HG_FORK
-        description = 'fork of vcs test'
-        repo_name = HG_REPO
-        response = self.app.post(url(controller='settings', action='fork_create',
-                                    repo_name=repo_name),
-                                    {'fork_name':fork_name,
-                                     'repo_type':'hg',
-                                     'description':description,
-                                     'private':'False'})
-
-        #test if we have a message that fork is ok
-        assert 'forked %s repository as %s' \
-                      % (repo_name, fork_name) in response.session['flash'][0], 'No flash message about fork'
-
-        #test if the fork was created in the database
-        fork_repo = self.sa.query(Repository).filter(Repository.repo_name == fork_name).one()
-
-        assert fork_repo.repo_name == fork_name, 'wrong name of repo name in new db fork repo'
-        assert fork_repo.fork.repo_name == repo_name, 'wrong fork parrent'
-
-
-        #test if fork is visible in the list ?
-        response = response.follow()
-
-
-        #check if fork is marked as fork
-        response = self.app.get(url(controller='summary', action='index',
-                                    repo_name=fork_name))
-
-        assert 'Fork of %s' % repo_name in response.body, 'no message about that this repo is a fork'
--- a/rhodecode/tests/functional/test_summary.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_summary.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,18 +7,22 @@
 
     def test_index(self):
         self.log_user()
+        ID = Repository.get_by_repo_name(HG_REPO).repo_id
         response = self.app.get(url(controller='summary',
-                                    action='index', repo_name=HG_REPO))
+                                    action='index',
+                                    repo_name=HG_REPO))
 
         #repo type
-        self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
-                        """title="Mercurial repository" alt="Mercurial """
-                        """repository" src="/images/icons/hgicon.png"/>"""
-                        in response.body)
-        self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
-                        """title="public repository" alt="public """
-                        """repository" src="/images/icons/lock_open.png"/>"""
-                        in response.body)
+        response.mustcontain(
+            """<img style="margin-bottom:2px" class="icon" """
+            """title="Mercurial repository" alt="Mercurial """
+            """repository" src="/images/icons/hgicon.png"/>"""
+        )
+        response.mustcontain(
+            """<img style="margin-bottom:2px" class="icon" """
+            """title="public repository" alt="public """
+            """repository" src="/images/icons/lock_open.png"/>"""
+        )
 
         #codes stats
         self._enable_stats()
@@ -26,7 +30,6 @@
         invalidate_cache('get_repo_cached_%s' % HG_REPO)
         response = self.app.get(url(controller='summary', action='index',
                                     repo_name=HG_REPO))
-
         response.mustcontain(
             """var data = [["py", {"count": 42, "desc": ["Python"]}], """
             """["rst", {"count": 11, "desc": ["Rst"]}], """
@@ -38,10 +41,26 @@
         )
 
         # clone url...
-        response.mustcontain("""<input type="text" id="clone_url" readonly="readonly" value="hg clone http://test_admin@localhost:80/%s" size="70"/>""" % HG_REPO)
+        response.mustcontain("""<input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/vcs_test_hg"/>""")
+        response.mustcontain("""<input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="http://test_admin@localhost:80/_1"/>""")
+
+    def test_index_by_id(self):
+        self.log_user()
+        ID = Repository.get_by_repo_name(HG_REPO).repo_id
+        response = self.app.get(url(controller='summary',
+                                    action='index',
+                                    repo_name='_%s' % ID))
+
+        #repo type
+        response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
+                        """title="Mercurial repository" alt="Mercurial """
+                        """repository" src="/images/icons/hgicon.png"/>""")
+        response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
+                        """title="public repository" alt="public """
+                        """repository" src="/images/icons/lock_open.png"/>""")
 
     def _enable_stats(self):
         r = Repository.get_by_repo_name(HG_REPO)
         r.enable_statistics = True
-        self.sa.add(r)
-        self.sa.commit()
+        self.Session.add(r)
+        self.Session.commit()
--- a/rhodecode/tests/functional/test_tags.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/functional/test_tags.py	Sun Feb 26 17:25:09 2012 +0200
@@ -5,9 +5,8 @@
     def test_index(self):
         self.log_user()
         response = self.app.get(url(controller='tags', action='index', repo_name=HG_REPO))
-        assert """<a href="/%s/changeset/27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</a>""" % HG_REPO in response.body, 'wrong info about tip tag'
-        assert """<a href="/%s/changeset/fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</a>""" % HG_REPO in response.body, 'wrong info about 0.1.4 tag'
-        assert """<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</a>""" % HG_REPO in response.body, 'wrong info about 0.1.3 tag'
-        assert """<a href="/%s/changeset/a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</a>""" % HG_REPO in response.body, 'wrong info about 0.1.2 tag'
-        assert """<a href="/%s/changeset/eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</a>""" % HG_REPO in response.body, 'wrong info about 0.1.1 tag'
-        # Test response...
+        response.mustcontain("""<a href="/%s/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/">tip</a>""" % HG_REPO)
+        response.mustcontain("""<a href="/%s/files/fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200/">0.1.4</a>""" % HG_REPO)
+        response.mustcontain("""<a href="/%s/files/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf/">0.1.3</a>""" % HG_REPO)
+        response.mustcontain("""<a href="/%s/files/a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720/">0.1.2</a>""" % HG_REPO)
+        response.mustcontain("""<a href="/%s/files/eb3a60fc964309c1a318b8dfe26aa2d1586c85ae/">0.1.1</a>""" % HG_REPO)
--- a/rhodecode/tests/rhodecode_crawler.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/rhodecode_crawler.py	Sun Feb 26 17:25:09 2012 +0200
@@ -6,12 +6,12 @@
     Test for crawling a project for memory usage
     This should be runned just as regular script together
     with a watch script that will show memory usage.
-    
+
     watch -n1 ./rhodecode/tests/mem_watch
 
     :created_on: Apr 21, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -31,11 +31,10 @@
 import cookielib
 import urllib
 import urllib2
-import vcs
 import time
 
 from os.path import join as jn
-
+from rhodecode.lib import vcs
 
 BASE_URI = 'http://127.0.0.1:5000/%s'
 PROJECT = 'CPython'
@@ -52,7 +51,6 @@
 urllib2.install_opener(o)
 
 
-
 def test_changelog_walk(pages=100):
     total_time = 0
     for i in range(1, pages):
@@ -67,7 +65,6 @@
         total_time += e
         print 'visited %s size:%s req:%s ms' % (full_uri, size, e)
 
-
     print 'total_time', total_time
     print 'average on req', total_time / float(pages)
 
@@ -103,6 +100,7 @@
     repo = vcs.get_repo(jn(PROJECT_PATH, PROJECT))
 
     from rhodecode.lib.compat import OrderedSet
+    from rhodecode.lib.vcs.exceptions import RepositoryError
 
     paths_ = OrderedSet([''])
     try:
@@ -117,7 +115,7 @@
             for f in files:
                 paths_.add(f.path)
 
-    except vcs.exception.RepositoryError, e:
+    except RepositoryError, e:
         pass
 
     cnt = 0
@@ -140,7 +138,6 @@
     print 'average on req', total_time / float(cnt)
 
 
-
 test_changelog_walk(40)
 time.sleep(2)
 test_changeset_walk(limit=100)
--- a/rhodecode/tests/test_concurency.py	Sun Feb 19 20:21:14 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,189 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    rhodecode.tests.test_hg_operations
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Test suite for making push/pull operations
-
-    :created_on: Dec 30, 2010
-    :copyright: (C) 2009-2011 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 shutil
-import logging
-from os.path import join as jn
-from os.path import dirname as dn
-
-from tempfile import _RandomNameSequence
-from subprocess import Popen, PIPE
-
-from paste.deploy import appconfig
-from pylons import config
-from sqlalchemy import engine_from_config
-
-from rhodecode.lib.utils import add_cache
-from rhodecode.model import init_model
-from rhodecode.model import meta
-from rhodecode.model.db import User, Repository
-from rhodecode.lib.auth import get_crypt_password
-
-from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO
-from rhodecode.config.environment import load_environment
-
-rel_path = dn(dn(dn(os.path.abspath(__file__))))
-conf = appconfig('config:development.ini', relative_to=rel_path)
-load_environment(conf.global_conf, conf.local_conf)
-
-add_cache(conf)
-
-USER = 'test_admin'
-PASS = 'test12'
-HOST = '127.0.0.1:5000'
-DEBUG = True
-log = logging.getLogger(__name__)
-
-
-class Command(object):
-
-    def __init__(self, cwd):
-        self.cwd = cwd
-
-    def execute(self, cmd, *args):
-        """Runs command on the system with given ``args``.
-        """
-
-        command = cmd + ' ' + ' '.join(args)
-        log.debug('Executing %s' % command)
-        if DEBUG:
-            print command
-        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
-        stdout, stderr = p.communicate()
-        if DEBUG:
-            print stdout, stderr
-        return stdout, stderr
-
-def get_session():
-    engine = engine_from_config(conf, 'sqlalchemy.db1.')
-    init_model(engine)
-    sa = meta.Session()
-    return sa
-
-
-def create_test_user(force=True):
-    print 'creating test user'
-    sa = get_session()
-
-    user = sa.query(User).filter(User.username == USER).scalar()
-
-    if force and user is not None:
-        print 'removing current user'
-        for repo in sa.query(Repository).filter(Repository.user == user).all():
-            sa.delete(repo)
-        sa.delete(user)
-        sa.commit()
-
-    if user is None or force:
-        print 'creating new one'
-        new_usr = User()
-        new_usr.username = USER
-        new_usr.password = get_crypt_password(PASS)
-        new_usr.email = 'mail@mail.com'
-        new_usr.name = 'test'
-        new_usr.lastname = 'lasttestname'
-        new_usr.active = True
-        new_usr.admin = True
-        sa.add(new_usr)
-        sa.commit()
-
-    print 'done'
-
-
-def create_test_repo(force=True):
-    print 'creating test repo'
-    from rhodecode.model.repo import RepoModel
-    sa = get_session()
-
-    user = sa.query(User).filter(User.username == USER).scalar()
-    if user is None:
-        raise Exception('user not found')
-
-
-    repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar()
-
-    if repo is None:
-        print 'repo not found creating'
-
-        form_data = {'repo_name':HG_REPO,
-                     'repo_type':'hg',
-                     'private':False,
-                     'clone_uri':'' }
-        rm = RepoModel(sa)
-        rm.base_path = '/home/hg'
-        rm.create(form_data, user)
-
-    print 'done'
-
-def set_anonymous_access(enable=True):
-    sa = get_session()
-    user = sa.query(User).filter(User.username == 'default').one()
-    user.active = enable
-    sa.add(user)
-    sa.commit()
-
-def get_anonymous_access():
-    sa = get_session()
-    return sa.query(User).filter(User.username == 'default').one().active
-
-
-#==============================================================================
-# TESTS
-#==============================================================================
-def test_clone_with_credentials(no_errors=False, repo=HG_REPO):
-    cwd = path = jn(TESTS_TMP_PATH, repo)
-
-
-    try:
-        shutil.rmtree(path, ignore_errors=True)
-        os.makedirs(path)
-        #print 'made dirs %s' % jn(path)
-    except OSError:
-        raise
-
-
-    clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
-                  {'user':USER,
-                   'pass':PASS,
-                   'host':HOST,
-                   'cloned_repo':repo,
-                   'dest':path + _RandomNameSequence().next()}
-
-    stdout, stderr = Command(cwd).execute('hg clone', clone_url)
-
-    if no_errors is False:
-        assert """adding file changes""" in stdout, 'no messages about cloning'
-        assert """abort""" not in stderr , 'got error from clone'
-
-if __name__ == '__main__':
-    try:
-        create_test_user(force=False)
-
-        for i in range(int(sys.argv[2])):
-            test_clone_with_credentials(repo=sys.argv[1])
-
-    except Exception, e:
-        sys.exit('stop on %s' % e)
--- a/rhodecode/tests/test_hg_operations.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/test_hg_operations.py	Sun Feb 26 17:25:09 2012 +0200
@@ -6,7 +6,8 @@
     Test suite for making push/pull operations
 
     :created_on: Dec 30, 2010
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -184,7 +185,6 @@
     if anonymous_access:
         print '\tenabled, disabling it '
         set_anonymous_access(enable=False)
-        time.sleep(1)
 
     clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
                   {'user':USER,
@@ -217,7 +217,6 @@
     if not anonymous_access:
         print '\tnot enabled, enabling it '
         set_anonymous_access(enable=True)
-        time.sleep(1)
 
     clone_url = 'http://%(host)s/%(cloned_repo)s %(dest)s' % \
                   {'user':USER,
@@ -386,7 +385,7 @@
 
     initial_logs = get_logs()
     print 'initial activity logs: %s' % len(initial_logs)
-
+    s = time.time()
     #test_push_modify_file()
     test_clone_with_credentials()
     test_clone_wrong_credentials()
@@ -399,3 +398,4 @@
     test_push_wrong_credentials()
 
     test_logs(initial_logs)
+    print 'finished ok in %.3f' % (time.time() - s)
--- a/rhodecode/tests/test_libs.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/test_libs.py	Sun Feb 26 17:25:09 2012 +0200
@@ -5,9 +5,9 @@
 
 
     Package for testing various lib/helper functions in rhodecode
-    
+
     :created_on: Jun 9, 2011
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -103,3 +103,12 @@
         for case in test_cases:
             self.assertEqual(str2bool(case[0]), case[1])
 
+
+    def test_mention_extractor(self):
+        from rhodecode.lib 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")
+        s = ['2one_more22', 'D', 'MARCIN', 'first', 'lukaszb',
+             'maRCiN', 'marcink', 'one']
+        self.assertEqual(s, extract_mentioned_users(sample))
--- a/rhodecode/tests/test_models.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/tests/test_models.py	Sun Feb 26 17:25:09 2012 +0200
@@ -4,15 +4,35 @@
 
 from rhodecode.model.repos_group import ReposGroupModel
 from rhodecode.model.repo import RepoModel
-from rhodecode.model.db import Group, User
+from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
+    UsersGroup, UsersGroupMember, Permission
 from sqlalchemy.exc import IntegrityError
+from rhodecode.model.user import UserModel
+
+from rhodecode.model.meta import Session
+from rhodecode.model.notification import NotificationModel
+from rhodecode.model.users_group import UsersGroupModel
+from rhodecode.lib.auth import AuthUser
+
+
+def _make_group(path, desc='desc', parent_id=None,
+                 skip_if_exists=False):
+
+    gr = RepoGroup.get_by_group_name(path)
+    if gr and skip_if_exists:
+        return gr
+
+    gr = ReposGroupModel().create(path, desc, parent_id)
+    Session.commit()
+    return gr
+
 
 class TestReposGroups(unittest.TestCase):
 
     def setUp(self):
-        self.g1 = self.__make_group('test1', skip_if_exists=True)
-        self.g2 = self.__make_group('test2', skip_if_exists=True)
-        self.g3 = self.__make_group('test3', skip_if_exists=True)
+        self.g1 = _make_group('test1', skip_if_exists=True)
+        self.g2 = _make_group('test2', skip_if_exists=True)
+        self.g3 = _make_group('test3', skip_if_exists=True)
 
     def tearDown(self):
         print 'out'
@@ -25,101 +45,81 @@
     def _check_folders(self):
         print os.listdir(TESTS_TMP_PATH)
 
-    def __make_group(self, path, desc='desc', parent_id=None,
-                     skip_if_exists=False):
-
-        gr = Group.get_by_group_name(path)
-        if gr and skip_if_exists:
-            return gr
-
-        form_data = dict(group_name=path,
-                         group_description=desc,
-                         group_parent_id=parent_id)
-        gr = ReposGroupModel().create(form_data)
-        return gr
-
     def __delete_group(self, id_):
         ReposGroupModel().delete(id_)
 
-
     def __update_group(self, id_, path, desc='desc', parent_id=None):
         form_data = dict(group_name=path,
                          group_description=desc,
-                         group_parent_id=parent_id)
+                         group_parent_id=parent_id,
+                         perms_updates=[],
+                         perms_new=[])
 
         gr = ReposGroupModel().update(id_, form_data)
         return gr
 
     def test_create_group(self):
-        g = self.__make_group('newGroup')
+        g = _make_group('newGroup')
         self.assertEqual(g.full_path, 'newGroup')
 
         self.assertTrue(self.__check_path('newGroup'))
 
-
     def test_create_same_name_group(self):
-        self.assertRaises(IntegrityError, lambda:self.__make_group('newGroup'))
-
+        self.assertRaises(IntegrityError, lambda:_make_group('newGroup'))
+        Session.rollback()
 
     def test_same_subgroup(self):
-        sg1 = self.__make_group('sub1', parent_id=self.g1.group_id)
+        sg1 = _make_group('sub1', parent_id=self.g1.group_id)
         self.assertEqual(sg1.parent_group, self.g1)
         self.assertEqual(sg1.full_path, 'test1/sub1')
         self.assertTrue(self.__check_path('test1', 'sub1'))
 
-        ssg1 = self.__make_group('subsub1', parent_id=sg1.group_id)
+        ssg1 = _make_group('subsub1', parent_id=sg1.group_id)
         self.assertEqual(ssg1.parent_group, sg1)
         self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
         self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
 
-
     def test_remove_group(self):
-        sg1 = self.__make_group('deleteme')
+        sg1 = _make_group('deleteme')
         self.__delete_group(sg1.group_id)
 
-        self.assertEqual(Group.get(sg1.group_id), None)
+        self.assertEqual(RepoGroup.get(sg1.group_id), None)
         self.assertFalse(self.__check_path('deteteme'))
 
-        sg1 = self.__make_group('deleteme', parent_id=self.g1.group_id)
+        sg1 = _make_group('deleteme', parent_id=self.g1.group_id)
         self.__delete_group(sg1.group_id)
 
-        self.assertEqual(Group.get(sg1.group_id), None)
+        self.assertEqual(RepoGroup.get(sg1.group_id), None)
         self.assertFalse(self.__check_path('test1', 'deteteme'))
 
-
     def test_rename_single_group(self):
-        sg1 = self.__make_group('initial')
+        sg1 = _make_group('initial')
 
         new_sg1 = self.__update_group(sg1.group_id, 'after')
         self.assertTrue(self.__check_path('after'))
-        self.assertEqual(Group.get_by_group_name('initial'), None)
-
+        self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
 
     def test_update_group_parent(self):
 
-        sg1 = self.__make_group('initial', parent_id=self.g1.group_id)
+        sg1 = _make_group('initial', parent_id=self.g1.group_id)
 
         new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
         self.assertTrue(self.__check_path('test1', 'after'))
-        self.assertEqual(Group.get_by_group_name('test1/initial'), None)
-
+        self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
 
         new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
         self.assertTrue(self.__check_path('test3', 'after'))
-        self.assertEqual(Group.get_by_group_name('test3/initial'), None)
-
+        self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
 
         new_sg1 = self.__update_group(sg1.group_id, 'hello')
         self.assertTrue(self.__check_path('hello'))
 
-        self.assertEqual(Group.get_by_group_name('hello'), new_sg1)
-
-
+        self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
 
     def test_subgrouping_with_repo(self):
 
-        g1 = self.__make_group('g1')
-        g2 = self.__make_group('g2')
+        g1 = _make_group('g1')
+        g2 = _make_group('g2')
 
         # create new repo
         form_data = dict(repo_name='john',
@@ -143,7 +143,6 @@
         RepoModel().update(r.repo_name, form_data)
         self.assertEqual(r.repo_name, 'g1/john')
 
-
         self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
         self.assertTrue(self.__check_path('g2', 'g1'))
 
@@ -151,3 +150,406 @@
         self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
 
 
+class TestUser(unittest.TestCase):
+    def __init__(self, methodName='runTest'):
+        Session.remove()
+        super(TestUser, self).__init__(methodName=methodName)
+
+    def test_create_and_remove(self):
+        usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe',
+                                     email=u'u232@rhodecode.org',
+                                     name=u'u1', lastname=u'u1')
+        Session.commit()
+        self.assertEqual(User.get_by_username(u'test_user'), usr)
+
+        # make users group
+        users_group = UsersGroupModel().create('some_example_group')
+        Session.commit()
+
+        UsersGroupModel().add_user_to_group(users_group, usr)
+        Session.commit()
+
+        self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
+        self.assertEqual(UsersGroupMember.query().count(), 1)
+        UserModel().delete(usr.user_id)
+        Session.commit()
+
+        self.assertEqual(UsersGroupMember.query().all(), [])
+
+
+class TestNotifications(unittest.TestCase):
+
+    def __init__(self, methodName='runTest'):
+        Session.remove()
+        self.u1 = UserModel().create_or_update(username=u'u1',
+                                        password=u'qweqwe',
+                                        email=u'u1@rhodecode.org',
+                                        name=u'u1', lastname=u'u1')
+        Session.commit()
+        self.u1 = self.u1.user_id
+
+        self.u2 = UserModel().create_or_update(username=u'u2',
+                                        password=u'qweqwe',
+                                        email=u'u2@rhodecode.org',
+                                        name=u'u2', lastname=u'u3')
+        Session.commit()
+        self.u2 = self.u2.user_id
+
+        self.u3 = UserModel().create_or_update(username=u'u3',
+                                        password=u'qweqwe',
+                                        email=u'u3@rhodecode.org',
+                                        name=u'u3', lastname=u'u3')
+        Session.commit()
+        self.u3 = self.u3.user_id
+
+        super(TestNotifications, self).__init__(methodName=methodName)
+
+    def _clean_notifications(self):
+        for n in Notification.query().all():
+            Session.delete(n)
+
+        Session.commit()
+        self.assertEqual(Notification.query().all(), [])
+
+    def tearDown(self):
+        self._clean_notifications()
+
+    def test_create_notification(self):
+        self.assertEqual([], Notification.query().all())
+        self.assertEqual([], UserNotification.query().all())
+
+        usrs = [self.u1, self.u2]
+        notification = NotificationModel().create(created_by=self.u1,
+                                           subject=u'subj', body=u'hi there',
+                                           recipients=usrs)
+        Session.commit()
+        u1 = User.get(self.u1)
+        u2 = User.get(self.u2)
+        u3 = User.get(self.u3)
+        notifications = Notification.query().all()
+        self.assertEqual(len(notifications), 1)
+
+        unotification = UserNotification.query()\
+            .filter(UserNotification.notification == notification).all()
+
+        self.assertEqual(notifications[0].recipients, [u1, u2])
+        self.assertEqual(notification.notification_id,
+                         notifications[0].notification_id)
+        self.assertEqual(len(unotification), len(usrs))
+        self.assertEqual([x.user.user_id for x in unotification], usrs)
+
+    def test_user_notifications(self):
+        self.assertEqual([], Notification.query().all())
+        self.assertEqual([], UserNotification.query().all())
+
+        notification1 = NotificationModel().create(created_by=self.u1,
+                                            subject=u'subj', body=u'hi there1',
+                                            recipients=[self.u3])
+        Session.commit()
+        notification2 = NotificationModel().create(created_by=self.u1,
+                                            subject=u'subj', body=u'hi there2',
+                                            recipients=[self.u3])
+        Session.commit()
+        u3 = Session.query(User).get(self.u3)
+
+        self.assertEqual(sorted([x.notification for x in u3.notifications]),
+                         sorted([notification2, notification1]))
+
+    def test_delete_notifications(self):
+        self.assertEqual([], Notification.query().all())
+        self.assertEqual([], UserNotification.query().all())
+
+        notification = NotificationModel().create(created_by=self.u1,
+                                           subject=u'title', body=u'hi there3',
+                                    recipients=[self.u3, self.u1, self.u2])
+        Session.commit()
+        notifications = Notification.query().all()
+        self.assertTrue(notification in notifications)
+
+        Notification.delete(notification.notification_id)
+        Session.commit()
+
+        notifications = Notification.query().all()
+        self.assertFalse(notification in notifications)
+
+        un = UserNotification.query().filter(UserNotification.notification
+                                             == notification).all()
+        self.assertEqual(un, [])
+
+    def test_delete_association(self):
+
+        self.assertEqual([], Notification.query().all())
+        self.assertEqual([], UserNotification.query().all())
+
+        notification = NotificationModel().create(created_by=self.u1,
+                                           subject=u'title', body=u'hi there3',
+                                    recipients=[self.u3, self.u1, self.u2])
+        Session.commit()
+
+        unotification = UserNotification.query()\
+                            .filter(UserNotification.notification ==
+                                    notification)\
+                            .filter(UserNotification.user_id == self.u3)\
+                            .scalar()
+
+        self.assertEqual(unotification.user_id, self.u3)
+
+        NotificationModel().delete(self.u3,
+                                   notification.notification_id)
+        Session.commit()
+
+        u3notification = UserNotification.query()\
+                            .filter(UserNotification.notification ==
+                                    notification)\
+                            .filter(UserNotification.user_id == self.u3)\
+                            .scalar()
+
+        self.assertEqual(u3notification, None)
+
+        # notification object is still there
+        self.assertEqual(Notification.query().all(), [notification])
+
+        #u1 and u2 still have assignments
+        u1notification = UserNotification.query()\
+                            .filter(UserNotification.notification ==
+                                    notification)\
+                            .filter(UserNotification.user_id == self.u1)\
+                            .scalar()
+        self.assertNotEqual(u1notification, None)
+        u2notification = UserNotification.query()\
+                            .filter(UserNotification.notification ==
+                                    notification)\
+                            .filter(UserNotification.user_id == self.u2)\
+                            .scalar()
+        self.assertNotEqual(u2notification, None)
+
+    def test_notification_counter(self):
+        self._clean_notifications()
+        self.assertEqual([], Notification.query().all())
+        self.assertEqual([], UserNotification.query().all())
+
+        NotificationModel().create(created_by=self.u1,
+                            subject=u'title', body=u'hi there_delete',
+                            recipients=[self.u3, self.u1])
+        Session.commit()
+
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u1), 1)
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u2), 0)
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u3), 1)
+
+        notification = NotificationModel().create(created_by=self.u1,
+                                           subject=u'title', body=u'hi there3',
+                                    recipients=[self.u3, self.u1, self.u2])
+        Session.commit()
+
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u1), 2)
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u2), 1)
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u3), 2)
+
+
+class TestUsers(unittest.TestCase):
+
+    def __init__(self, methodName='runTest'):
+        super(TestUsers, self).__init__(methodName=methodName)
+
+    def setUp(self):
+        self.u1 = UserModel().create_or_update(username=u'u1',
+                                        password=u'qweqwe',
+                                        email=u'u1@rhodecode.org',
+                                        name=u'u1', lastname=u'u1')
+
+    def tearDown(self):
+        perm = Permission.query().all()
+        for p in perm:
+            UserModel().revoke_perm(self.u1, p)
+
+        UserModel().delete(self.u1)
+        Session.commit()
+
+    def test_add_perm(self):
+        perm = Permission.query().all()[0]
+        UserModel().grant_perm(self.u1, perm)
+        Session.commit()
+        self.assertEqual(UserModel().has_perm(self.u1, perm), True)
+
+    def test_has_perm(self):
+        perm = Permission.query().all()
+        for p in perm:
+            has_p = UserModel().has_perm(self.u1, p)
+            self.assertEqual(False, has_p)
+
+    def test_revoke_perm(self):
+        perm = Permission.query().all()[0]
+        UserModel().grant_perm(self.u1, perm)
+        Session.commit()
+        self.assertEqual(UserModel().has_perm(self.u1, perm), True)
+
+        #revoke
+        UserModel().revoke_perm(self.u1, perm)
+        Session.commit()
+        self.assertEqual(UserModel().has_perm(self.u1, perm), False)
+
+
+class TestPermissions(unittest.TestCase):
+    def __init__(self, methodName='runTest'):
+        super(TestPermissions, self).__init__(methodName=methodName)
+
+    def setUp(self):
+        self.u1 = UserModel().create_or_update(
+            username=u'u1', password=u'qweqwe',
+            email=u'u1@rhodecode.org', name=u'u1', lastname=u'u1'
+        )
+        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
+        )
+        Session.commit()
+
+    def tearDown(self):
+        UserModel().delete(self.u1)
+        UserModel().delete(self.a1)
+        if hasattr(self, 'g1'):
+            ReposGroupModel().delete(self.g1.group_id)
+        if hasattr(self, 'g2'):
+            ReposGroupModel().delete(self.g2.group_id)
+
+        if hasattr(self, 'ug1'):
+            UsersGroupModel().delete(self.ug1, force=True)
+
+        Session.commit()
+
+    def test_default_perms_set(self):
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        perms = {
+            'repositories_groups': {},
+            'global': set([u'hg.create.repository', u'repository.read',
+                           u'hg.register.manual_activate']),
+            'repositories': {u'vcs_test_hg': u'repository.read'}
+        }
+        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
+                         perms['repositories'][HG_REPO])
+        new_perm = 'repository.write'
+        RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
+        Session.commit()
+
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], new_perm)
+
+    def test_default_admin_perms_set(self):
+        a1_auth = AuthUser(user_id=self.a1.user_id)
+        perms = {
+            'repositories_groups': {},
+            'global': set([u'hg.admin']),
+            'repositories': {u'vcs_test_hg': u'repository.admin'}
+        }
+        self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
+                         perms['repositories'][HG_REPO])
+        new_perm = 'repository.write'
+        RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1, perm=new_perm)
+        Session.commit()
+        # cannot really downgrade admins permissions !? they still get's set as
+        # admin !
+        u1_auth = AuthUser(user_id=self.a1.user_id)
+        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
+                         perms['repositories'][HG_REPO])
+
+    def test_default_group_perms(self):
+        self.g1 = _make_group('test1', skip_if_exists=True)
+        self.g2 = _make_group('test2', skip_if_exists=True)
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        perms = {
+            'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
+            'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']),
+            'repositories': {u'vcs_test_hg': u'repository.read'}
+        }
+        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
+                         perms['repositories'][HG_REPO])
+        self.assertEqual(u1_auth.permissions['repositories_groups'],
+                         perms['repositories_groups'])
+
+    def test_default_admin_group_perms(self):
+        self.g1 = _make_group('test1', skip_if_exists=True)
+        self.g2 = _make_group('test2', skip_if_exists=True)
+        a1_auth = AuthUser(user_id=self.a1.user_id)
+        perms = {
+            'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
+            'global': set(['hg.admin']),
+            'repositories': {u'vcs_test_hg': 'repository.admin'}
+        }
+
+        self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
+                         perms['repositories'][HG_REPO])
+        self.assertEqual(a1_auth.permissions['repositories_groups'],
+                         perms['repositories_groups'])
+
+    def test_propagated_permission_from_users_group(self):
+        # make group
+        self.ug1 = UsersGroupModel().create('G1')
+        # add user to group
+        UsersGroupModel().add_user_to_group(self.ug1, self.u1)
+
+        # set permission to lower
+        new_perm = 'repository.none'
+        RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
+        Session.commit()
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
+                         new_perm)
+
+        # grant perm for group this should override permission from user
+        new_perm = 'repository.write'
+        RepoModel().grant_users_group_permission(repo=HG_REPO,
+                                                 group_name=self.ug1,
+                                                 perm=new_perm)
+        # check perms
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        perms = {
+            'repositories_groups': {},
+            'global': set([u'hg.create.repository', u'repository.read',
+                           u'hg.register.manual_activate']),
+            'repositories': {u'vcs_test_hg': u'repository.read'}
+        }
+        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
+                         new_perm)
+        self.assertEqual(u1_auth.permissions['repositories_groups'],
+                         perms['repositories_groups'])
+
+    def test_propagated_permission_from_users_group_lower_weight(self):
+        # make group
+        self.ug1 = UsersGroupModel().create('G1')
+        # add user to group
+        UsersGroupModel().add_user_to_group(self.ug1, self.u1)
+
+        # set permission to lower
+        new_perm_h = 'repository.write'
+        RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
+                                          perm=new_perm_h)
+        Session.commit()
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
+                         new_perm_h)
+
+        # grant perm for group this should NOT override permission from user
+        # since it's lower than granted
+        new_perm_l = 'repository.read'
+        RepoModel().grant_users_group_permission(repo=HG_REPO,
+                                                 group_name=self.ug1,
+                                                 perm=new_perm_l)
+        # check perms
+        u1_auth = AuthUser(user_id=self.u1.user_id)
+        perms = {
+            'repositories_groups': {},
+            'global': set([u'hg.create.repository', u'repository.read',
+                           u'hg.register.manual_activate']),
+            'repositories': {u'vcs_test_hg': u'repository.write'}
+        }
+        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
+                         new_perm_h)
+        self.assertEqual(u1_auth.permissions['repositories_groups'],
+                         perms['repositories_groups'])
--- a/rhodecode/websetup.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/rhodecode/websetup.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,7 +7,7 @@
 
     :created_on: Dec 11, 2010
     :author: marcink
-    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
@@ -23,11 +23,11 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import os
 import logging
 
 from rhodecode.config.environment import load_environment
 from rhodecode.lib.db_manage import DbManage
+from rhodecode.model.meta import Session
 
 
 log = logging.getLogger(__name__)
@@ -45,5 +45,5 @@
     dbmanage.admin_prompt()
     dbmanage.create_permissions()
     dbmanage.populate_default_permissions()
-
+    Session.commit()
     load_environment(conf.global_conf, conf.local_conf, initial=True)
--- a/setup.cfg	Sun Feb 19 20:21:14 2012 +0200
+++ b/setup.cfg	Sun Feb 26 17:25:09 2012 +0200
@@ -11,6 +11,8 @@
 with-pylons=test.ini
 detailed-errors=1
 nologcapture=1
+#pdb=1
+#pdb-failures=1
 
 # Babel configuration
 [compile_catalog]
--- a/setup.py	Sun Feb 19 20:21:14 2012 +0200
+++ b/setup.py	Sun Feb 26 17:25:09 2012 +0200
@@ -7,20 +7,21 @@
 if __py_version__ < (2, 5):
     raise Exception('RhodeCode requires python 2.5 or later')
 
-
 dependency_links = [
 ]
 
-classifiers = ['Development Status :: 5 - Production/Stable',
-               'Environment :: Web Environment',
-               'Framework :: Pylons',
-               'Intended Audience :: Developers',
-               'License :: OSI Approved :: GNU General Public License (GPL)',
-               'Operating System :: OS Independent',
-               'Programming Language :: Python',
-               'Programming Language :: Python :: 2.5',
-               'Programming Language :: Python :: 2.6',
-               'Programming Language :: Python :: 2.7', ]
+classifiers = [
+    'Development Status :: 5 - Production/Stable',
+    'Environment :: Web Environment',
+    'Framework :: Pylons',
+    'Intended Audience :: Developers',
+    'License :: OSI Approved :: GNU General Public License (GPL)',
+    'Operating System :: OS Independent',
+    'Programming Language :: Python',
+    'Programming Language :: Python :: 2.5',
+    'Programming Language :: Python :: 2.6',
+    'Programming Language :: Python :: 2.7',
+]
 
 
 # additional files from project that goes somewhere in the filesystem
--- a/test.ini	Sun Feb 19 20:21:14 2012 +0200
+++ b/test.ini	Sun Feb 26 17:25:09 2012 +0200
@@ -51,6 +51,8 @@
 force_https = false
 commit_parse_limit = 25
 use_gravatar = true
+container_auth_enabled = false
+proxypass_auth_enabled = false
 
 ####################################
 ###        CELERY CONFIG        ####
@@ -87,22 +89,28 @@
 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
-beaker.cache.super_short_term.expire=10
+beaker.cache.super_short_term.expire=1
+beaker.cache.super_short_term.key_length = 256
 
 beaker.cache.short_term.type=memory
 beaker.cache.short_term.expire=60
+beaker.cache.short_term.key_length = 256
 
 beaker.cache.long_term.type=memory
 beaker.cache.long_term.expire=36000
+beaker.cache.long_term.key_length = 256
 
 beaker.cache.sql_cache_short.type=memory
-beaker.cache.sql_cache_short.expire=10
+beaker.cache.sql_cache_short.expire=1
+beaker.cache.sql_cache_short.key_length = 256
 
 beaker.cache.sql_cache_med.type=memory
 beaker.cache.sql_cache_med.expire=360
+beaker.cache.sql_cache_med.key_length = 256
 
 beaker.cache.sql_cache_long.type=file
 beaker.cache.sql_cache_long.expire=3600
+beaker.cache.sql_cache_long.key_length = 256
 
 ####################################
 ###       BEAKER SESSION        ####
@@ -143,7 +151,7 @@
 #########################################################
 sqlalchemy.db1.url = sqlite:///%(here)s/test.db
 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
-#sqlalchemy.db1.echo = False
+#sqlalchemy.db1.echo = false
 #sqlalchemy.db1.pool_recycle = 3600
 sqlalchemy.convert_unicode = true