changeset 3540:6e8027c2f49c beta

Merge
author Leonardo <leo@unity3d.com>
date Mon, 11 Mar 2013 17:09:43 +0100
parents c65b440540da (current diff) 7174ee850baa (diff)
children 8fae93880c30
files rhodecode/lib/base.py rhodecode/lib/helpers.py rhodecode/lib/utils2.py rhodecode/public/css/style.css rhodecode/public/js/graph.js rhodecode/public/js/rhodecode.js rhodecode/templates/base/root.html rhodecode/templates/changelog/changelog.html
diffstat 69 files changed, 1032 insertions(+), 432 deletions(-) [+]
line wrap: on
line diff
--- a/development.ini	Mon Mar 11 16:43:41 2013 +0100
+++ b/development.ini	Mon Mar 11 17:09:43 2013 +0100
@@ -65,6 +65,8 @@
 lang = en
 cache_dir = %(here)s/data
 index_dir = %(here)s/data/index
+# set this path to use archive download cache
+#archive_cache_dir = /tmp/rhodecode_tarballcache
 app_instance_uuid = rc-develop
 cut_off_limit = 256000
 vcs_full_cache = True
@@ -154,6 +156,11 @@
 ## handling that. Set this variable to 403 to return HTTPForbidden
 auth_ret_code =
 
+## locking return code. When repository is locked return this HTTP code. 2XX
+## codes don't break the transactions while 4XX codes do
+lock_ret_code = 423
+
+
 ####################################
 ###        CELERY CONFIG        ####
 ####################################
--- a/docs/api/api.rst	Mon Mar 11 16:43:41 2013 +0100
+++ b/docs/api/api.rst	Mon Mar 11 17:09:43 2013 +0100
@@ -178,7 +178,8 @@
 ----
 
 Set locking state on given repository by given user. If userid param is skipped
-, then it is set to id of user whos calling this method.
+, then it is set to id of user whos calling this method. If locked param is skipped
+then function shows current lock state of given repo.
 This command can be executed only using api_key belonging to user with admin
 rights or regular user that have admin or write access to repository.
 
@@ -190,7 +191,7 @@
     args :    {
                 "repoid" : "<reponame or repo_id>"
                 "userid" : "<user_id or username = Optional(=apiuser)>",
-                "locked" : "<bool true|false>"
+                "locked" : "<bool true|false = Optional(=None)>"
               }
 
 OUTPUT::
--- a/docs/changelog.rst	Mon Mar 11 16:43:41 2013 +0100
+++ b/docs/changelog.rst	Mon Mar 11 17:09:43 2013 +0100
@@ -16,6 +16,27 @@
 fixes
 +++++
 
+1.5.4 (**2013-03-13**)
+----------------------
+
+news
+++++
+
+
+fixes
++++++
+
+- fixed webtest dependency issues
+- fixed issues with celery tasks for password reset
+- fixed #763 gravatar helper function should fallback into default image
+  if email is empty
+- fixes #762 user global activation flag is also respected for LDAP created
+  accounts
+- use password obfuscate when clonning a remote repo with credentials inside
+- fixed issue with renaming repos group together with changing parents
+- disallow cloning from file:/// URIs
+- handle all cases with multiple IP addresses in proxy headers
+
 1.5.3 (**2013-02-12**)
 ----------------------
 
@@ -139,8 +160,8 @@
   When this is used together with mercurial internal translation system
   it can lead to UnicodeDecodeErrors
 - fixes #645 Fix git handler when doing delete remote branch
-- implements #649 added two seperate method for author and commiter to VCS
-  changeset class switch author for git backed to be the real author not commiter
+- implements #649 added two seperate method for author and committer to VCS
+  changeset class switch author for git backed to be the real author not committer
 - fix issue #504 RhodeCode is showing different versions of README on
   different summary page loads
 - implemented #658 Changing username in LDAP-Mode should not be allowed.
--- a/docs/setup.rst	Mon Mar 11 16:43:41 2013 +0100
+++ b/docs/setup.rst	Mon Mar 11 17:09:43 2013 +0100
@@ -478,7 +478,7 @@
 
 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,
+This affects many parts in rhodecode including committers 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.
--- a/docs/theme/nature/static/pygments.css	Mon Mar 11 16:43:41 2013 +0100
+++ b/docs/theme/nature/static/pygments.css	Mon Mar 11 17:09:43 2013 +0100
@@ -51,4 +51,4 @@
 .vc { color: #ff99ff } /* Name.Variable.Class */
 .vg { color: #ff99ff } /* Name.Variable.Global */
 .vi { color: #ff99ff } /* Name.Variable.Instance */
-.il { color: #009999 } /* Literal.Number.Integer.Long */
+.il { color: #009999 } /* Literal.Number.Integer.Long */
\ No newline at end of file
--- a/production.ini	Mon Mar 11 16:43:41 2013 +0100
+++ b/production.ini	Mon Mar 11 17:09:43 2013 +0100
@@ -65,6 +65,8 @@
 lang = en
 cache_dir = %(here)s/data
 index_dir = %(here)s/data/index
+# set this path to use archive download cache
+#archive_cache_dir = /tmp/rhodecode_tarballcache
 app_instance_uuid = rc-production
 cut_off_limit = 256000
 vcs_full_cache = True
@@ -154,6 +156,11 @@
 ## handling that. Set this variable to 403 to return HTTPForbidden
 auth_ret_code =
 
+## locking return code. When repository is locked return this HTTP code. 2XX
+## codes don't break the transactions while 4XX codes do
+lock_ret_code = 423
+
+
 ####################################
 ###        CELERY CONFIG        ####
 ####################################
--- a/rhodecode/config/deployment.ini_tmpl	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/config/deployment.ini_tmpl	Mon Mar 11 17:09:43 2013 +0100
@@ -65,6 +65,8 @@
 lang = en
 cache_dir = %(here)s/data
 index_dir = %(here)s/data/index
+# set this path to use archive download cache
+#archive_cache_dir = /tmp/rhodecode_tarballcache
 app_instance_uuid = ${app_instance_uuid}
 cut_off_limit = 256000
 vcs_full_cache = True
@@ -154,6 +156,11 @@
 ## handling that. Set this variable to 403 to return HTTPForbidden
 auth_ret_code =
 
+## locking return code. When repository is locked return this HTTP code. 2XX
+## codes don't break the transactions while 4XX codes do
+lock_ret_code = 423
+
+
 ####################################
 ###        CELERY CONFIG        ####
 ####################################
--- a/rhodecode/config/middleware.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/config/middleware.py	Mon Mar 11 17:09:43 2013 +0100
@@ -15,6 +15,7 @@
 from rhodecode.lib.middleware.simplegit import SimpleGit
 from rhodecode.lib.middleware.https_fixup import HttpsFixup
 from rhodecode.config.environment import load_environment
+from rhodecode.lib.middleware.wrapper import RequestWrapper
 
 
 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
@@ -55,7 +56,7 @@
 
         from rhodecode.lib.middleware.sentry import Sentry
         from rhodecode.lib.middleware.errormator import Errormator
-        if Errormator:
+        if Errormator and asbool(config['app_conf'].get('errormator')):
             app = Errormator(app, config)
         elif Sentry:
             app = Sentry(app, config)
@@ -67,7 +68,7 @@
         # need any pylons stack middleware in them
         app = SimpleHg(app, config)
         app = SimpleGit(app, config)
-
+        app = RequestWrapper(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	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/config/routing.py	Mon Mar 11 17:09:43 2013 +0100
@@ -56,6 +56,18 @@
         repos_group_name = match_dict.get('group_name')
         return is_valid_repos_group(repos_group_name, config['base_path'])
 
+    def check_group_skip_path(environ, match_dict):
+        """
+        check for valid repository group for proper 404 handling, but skips
+        verification of existing path
+
+        :param environ:
+        :param match_dict:
+        """
+        repos_group_name = match_dict.get('group_name')
+        return is_valid_repos_group(repos_group_name, config['base_path'],
+                                    skip_path_check=True)
+
     def check_int(environ, match_dict):
         return match_dict.get('id').isdigit()
 
@@ -171,9 +183,10 @@
                                                    function=check_group))
         m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
                   action="delete", conditions=dict(method=["DELETE"],
-                                                   function=check_group))
+                                                   function=check_group_skip_path))
         m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
-                  action="edit", conditions=dict(method=["GET"],))
+                  action="edit", conditions=dict(method=["GET"],
+                                                 function=check_group))
         m.connect("formatted_edit_repos_group",
                   "/repos_groups/{group_name:.*?}.{format}/edit",
                   action="edit", conditions=dict(method=["GET"],
--- a/rhodecode/controllers/admin/repos_groups.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/controllers/admin/repos_groups.py	Mon Mar 11 17:09:43 2013 +0100
@@ -251,31 +251,25 @@
         repos = gr.repositories.all()
         if repos:
             h.flash(_('This group contains %s repositores and cannot be '
-                      'deleted') % len(repos),
-                    category='error')
+                      'deleted') % len(repos), category='warning')
+            return redirect(url('repos_groups'))
+
+        children = gr.children.all()
+        if children:
+            h.flash(_('This group contains %s subgroups and cannot be deleted'
+                      % (len(children))), category='warning')
             return redirect(url('repos_groups'))
 
         try:
             ReposGroupModel().delete(group_name)
             Session().commit()
-            h.flash(_('removed repos group %s') % gr.group_name,
+            h.flash(_('removed repos group %s') % group_name,
                     category='success')
             #TODO: in future action_logger(, '', '', '', self.sa)
-        except IntegrityError, e:
-            if str(e.message).find('groups_group_parent_id_fkey') != -1:
-                log.error(traceback.format_exc())
-                h.flash(_('Cannot delete this group it still contains '
-                          'subgroups'),
-                        category='warning')
-            else:
-                log.error(traceback.format_exc())
-                h.flash(_('error occurred during deletion of repos '
-                          'group %s') % gr.group_name, category='error')
-
         except Exception:
             log.error(traceback.format_exc())
             h.flash(_('error occurred during deletion of repos '
-                      'group %s') % gr.group_name, category='error')
+                      'group %s') % group_name, category='error')
 
         return redirect(url('repos_groups'))
 
--- a/rhodecode/controllers/admin/settings.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/controllers/admin/settings.py	Mon Mar 11 17:09:43 2013 +0100
@@ -38,7 +38,7 @@
 from rhodecode.lib import helpers as h
 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
     HasPermissionAnyDecorator, NotAnonymous, HasPermissionAny,\
-    HasReposGroupPermissionAll, HasReposGroupPermissionAny
+    HasReposGroupPermissionAll, HasReposGroupPermissionAny, AuthUser
 from rhodecode.lib.base import BaseController, render
 from rhodecode.lib.celerylib import tasks, run_task
 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
@@ -409,6 +409,8 @@
         # url('admin_settings_my_account')
 
         c.user = User.get(self.rhodecode_user.user_id)
+        c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id,
+                               ip_addr=self.ip_addr)
         c.ldap_dn = c.user.ldap_dn
 
         if c.user.username == 'default':
@@ -440,6 +442,8 @@
         # url('admin_settings_my_account_update', id=ID)
         uid = self.rhodecode_user.user_id
         c.user = User.get(self.rhodecode_user.user_id)
+        c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id,
+                               ip_addr=self.ip_addr)
         c.ldap_dn = c.user.ldap_dn
         email = self.rhodecode_user.email
         _form = UserForm(edit=True,
--- a/rhodecode/controllers/api/api.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/controllers/api/api.py	Mon Mar 11 17:09:43 2013 +0100
@@ -27,14 +27,14 @@
 
 import traceback
 import logging
-from pylons.controllers.util import abort
 
 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
 from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
     HasPermissionAllDecorator, HasPermissionAnyDecorator, \
     HasPermissionAnyApi, HasRepoPermissionAnyApi
 from rhodecode.lib.utils import map_groups, repo2db_mapper
-from rhodecode.lib.utils2 import str2bool
+from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_int
+from rhodecode.lib import helpers as h
 from rhodecode.model.meta import Session
 from rhodecode.model.scm import ScmModel
 from rhodecode.model.repo import RepoModel
@@ -42,6 +42,7 @@
 from rhodecode.model.users_group import UserGroupModel
 from rhodecode.model.permission import PermissionModel
 from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap
+from rhodecode.lib.compat import json
 
 log = logging.getLogger(__name__)
 
@@ -229,7 +230,8 @@
                 'Error occurred during cache invalidation action'
             )
 
-    def lock(self, apiuser, repoid, locked, userid=Optional(OAttr('apiuser'))):
+    def lock(self, apiuser, repoid, locked=Optional(None),
+             userid=Optional(OAttr('apiuser'))):
         """
         Set locking state on particular repository by given user, if
         this command is runned by non-admin account userid is set to user
@@ -257,21 +259,77 @@
 
         if isinstance(userid, Optional):
             userid = apiuser.user_id
+
         user = get_user_or_error(userid)
-        locked = str2bool(locked)
-        try:
-            if locked:
-                Repository.lock(repo, user.user_id)
+
+        if isinstance(locked, Optional):
+            lockobj = Repository.getlock(repo)
+
+            if lockobj[0] is None:
+                return ('Repo `%s` not locked. Locked=`False`.'
+                        % (repo.repo_name))
             else:
-                Repository.unlock(repo)
+                userid, time_ = lockobj
+                user = get_user_or_error(userid)
+
+                return ('Repo `%s` locked by `%s`. Locked=`True`. '
+                        'Locked since: `%s`'
+                    % (repo.repo_name, user.username,
+                       json.dumps(time_to_datetime(time_))))
+
+        else:
+            locked = str2bool(locked)
+            try:
+                if locked:
+                    Repository.lock(repo, user.user_id)
+                else:
+                    Repository.unlock(repo)
+
+                return ('User `%s` set lock state for repo `%s` to `%s`'
+                        % (user.username, repo.repo_name, locked))
+            except Exception:
+                log.error(traceback.format_exc())
+                raise JSONRPCError(
+                    'Error occurred locking repository `%s`' % repo.repo_name
+                )
 
-            return ('User `%s` set lock state for repo `%s` to `%s`'
-                    % (user.username, repo.repo_name, locked))
-        except Exception:
-            log.error(traceback.format_exc())
-            raise JSONRPCError(
-                'Error occurred locking repository `%s`' % repo.repo_name
-            )
+    def get_locks(self, apiuser, userid=Optional(OAttr('apiuser'))):
+        """
+        Get all locks for given userid, if
+        this command is runned by non-admin account userid is set to user
+        who is calling this method, thus returning locks for himself
+
+        :param apiuser:
+        :param userid:
+        """
+        if HasPermissionAnyApi('hg.admin')(user=apiuser):
+            pass
+        else:
+            #make sure normal user does not pass someone else userid,
+            #he is not allowed to do that
+            if not isinstance(userid, Optional) and userid != apiuser.user_id:
+                raise JSONRPCError(
+                    'userid is not the same as your user'
+                )
+        ret = []
+        if isinstance(userid, Optional):
+            user = None
+        else:
+            user = get_user_or_error(userid)
+
+        #show all locks
+        for r in Repository.getAll():
+            userid, time_ = r.locked
+            if time_:
+                _api_data = r.get_api_data()
+                # if we use userfilter just show the locks for this user
+                if user:
+                    if safe_int(userid) == user.user_id:
+                        ret.append(_api_data)
+                else:
+                    ret.append(_api_data)
+
+        return ret
 
     @HasPermissionAllDecorator('hg.admin')
     def show_ip(self, apiuser, userid):
--- a/rhodecode/controllers/compare.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/controllers/compare.py	Mon Mar 11 17:09:43 2013 +0100
@@ -89,11 +89,17 @@
         # other_ref will be evaluated in other_repo
         other_ref = (other_ref_type, other_ref)
         other_repo = request.GET.get('other_repo', org_repo)
+        # If merge is True:
+        #   Show what org would get if merged with other:
+        #   List changesets that are ancestors of other but not of org.
+        #   New changesets in org is thus ignored.
+        #   Diff will be from common ancestor, and merges of org to other will thus be ignored.
+        # If merge is False:
+        #   Make a raw diff from org to other, no matter if related or not.
+        #   Changesets in one and not in the other will be ignored
+        merge = bool(request.GET.get('merge'))
         # fulldiff disables cut_off_limit
         c.fulldiff = request.GET.get('fulldiff')
-        # only consider this range of changesets
-        rev_start = request.GET.get('rev_start')
-        rev_end = request.GET.get('rev_end')
         # partial uses compare_cs.html template directly
         partial = request.environ.get('HTTP_X_PARTIAL_XHR')
         # as_form puts hidden input field with changeset revisions
@@ -103,7 +109,8 @@
             repo_name=other_repo,
             org_ref_type=other_ref[0], org_ref=other_ref[1],
             other_repo=org_repo,
-            other_ref_type=org_ref[0], other_ref=org_ref[1])
+            other_ref_type=org_ref[0], other_ref=org_ref[1],
+            merge=merge or '')
 
         org_repo = Repository.get_by_repo_name(org_repo)
         other_repo = Repository.get_by_repo_name(other_repo)
@@ -133,37 +140,23 @@
         c.org_ref_type = org_ref[0]
         c.other_ref_type = other_ref[0]
 
-        if rev_start and rev_end:
-            # swap revs with cherry picked ones, save them for display
-            #org_ref = ('rev', rev_start)
-            #other_ref = ('rev', rev_end)
-            c.org_ref = rev_start[:12]
-            c.other_ref = rev_end[:12]
-            # get parent of
-            # rev start to include it in the diff
-            _cs = other_repo.scm_instance.get_changeset(rev_start)
-            rev_start = _cs.parents[0].raw_id if _cs.parents else EmptyChangeset().raw_id
-            org_ref = ('rev', rev_start)
-            other_ref = ('rev', rev_end)
-            #if we cherry pick it's not remote, make the other_repo org_repo
-            org_repo = other_repo
-
-        c.cs_ranges, ancestor = PullRequestModel().get_compare_data(
-            org_repo, org_ref, other_repo, other_ref)
+        c.cs_ranges, c.ancestor = PullRequestModel().get_compare_data(
+            org_repo, org_ref, other_repo, other_ref, merge)
 
         c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
                                                    c.cs_ranges])
         if partial:
+            assert c.ancestor
             return render('compare/compare_cs.html')
 
-        if ancestor and org_repo != other_repo:
+        if c.ancestor:
+            assert merge
             # case we want a simple diff without incoming changesets,
             # previewing what will be merged.
-            # Make the diff on the forked repo, with
-            # revision that is common ancestor
+            # Make the diff on the other repo (which is known to have other_ref)
             log.debug('Using ancestor %s as org_ref instead of %s'
-                      % (ancestor, org_ref))
-            org_ref = ('rev', ancestor)
+                      % (c.ancestor, org_ref))
+            org_ref = ('rev', c.ancestor)
             org_repo = other_repo
 
         diff_limit = self.cut_off_limit if not c.fulldiff else None
--- a/rhodecode/controllers/files.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/controllers/files.py	Mon Mar 11 17:09:43 2013 +0100
@@ -27,6 +27,7 @@
 import logging
 import traceback
 import tempfile
+import shutil
 
 from pylons import request, response, tmpl_context as c, url
 from pylons.i18n.translation import _
@@ -315,7 +316,7 @@
             try:
                 self.scm_model.commit_change(repo=c.rhodecode_repo,
                                              repo_name=repo_name, cs=c.cs,
-                                             user=self.rhodecode_user,
+                                             user=self.rhodecode_user.user_id,
                                              author=author, message=message,
                                              content=content, f_path=f_path)
                 h.flash(_('Successfully committed to %s') % f_path,
@@ -378,7 +379,7 @@
             try:
                 self.scm_model.create_node(repo=c.rhodecode_repo,
                                            repo_name=repo_name, cs=c.cs,
-                                           user=self.rhodecode_user,
+                                           user=self.rhodecode_user.user_id,
                                            author=author, message=message,
                                            content=content, f_path=node_path)
                 h.flash(_('Successfully committed to %s') % node_path,
@@ -429,11 +430,40 @@
             return _('Empty repository')
         except (ImproperArchiveTypeError, KeyError):
             return _('Unknown archive type')
+        # archive cache
+        from rhodecode import CONFIG
+        rev_name = cs.raw_id[:12]
+        archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
+                                    safe_str(rev_name), ext)
 
-        fd, archive = tempfile.mkstemp()
-        t = open(archive, 'wb')
-        cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
-        t.close()
+        use_cached_archive = False  # defines if we use cached version of archive
+        archive_cache_enabled = CONFIG.get('archive_cache_dir')
+        if not subrepos and archive_cache_enabled:
+            #check if we it's ok to write
+            if not os.path.isdir(CONFIG['archive_cache_dir']):
+                os.makedirs(CONFIG['archive_cache_dir'])
+            cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
+            if os.path.isfile(cached_archive_path):
+                log.debug('Found cached archive in %s' % cached_archive_path)
+                fd, archive = None, cached_archive_path
+                use_cached_archive = True
+            else:
+                log.debug('Archive %s is not yet cached' % (archive_name))
+
+        if not use_cached_archive:
+            #generate new archive
+            try:
+                fd, archive = tempfile.mkstemp()
+                t = open(archive, 'wb')
+                log.debug('Creating new temp archive in %s' % archive)
+                cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
+                if archive_cache_enabled:
+                    #if we generated the archive and use cache rename that
+                    log.debug('Storing new archive in %s' % cached_archive_path)
+                    shutil.move(archive, cached_archive_path)
+                    archive = cached_archive_path
+            finally:
+                t.close()
 
         def get_chunked_archive(archive):
             stream = open(archive, 'rb')
@@ -441,13 +471,15 @@
                 data = stream.read(16 * 1024)
                 if not data:
                     stream.close()
-                    os.close(fd)
-                    os.remove(archive)
+                    if fd:  # fd means we used temporary file
+                        os.close(fd)
+                    if not archive_cache_enabled:
+                        log.debug('Destroing temp archive %s' % archive)
+                        os.remove(archive)
                     break
                 yield data
 
-        response.content_disposition = str('attachment; filename=%s-%s%s' \
-                                           % (repo_name, revision[:12], ext))
+        response.content_disposition = str('attachment; filename=%s' % (archive_name))
         response.content_type = str(content_type)
         return get_chunked_archive(archive)
 
--- a/rhodecode/controllers/pullrequests.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/controllers/pullrequests.py	Mon Mar 11 17:09:43 2013 +0100
@@ -52,6 +52,7 @@
 from rhodecode.model.comment import ChangesetCommentsModel
 from rhodecode.model.changeset_status import ChangesetStatusModel
 from rhodecode.model.forms import PullRequestForm
+from mercurial import scmutil
 
 log = logging.getLogger(__name__)
 
@@ -67,7 +68,7 @@
         c.users_array = repo_model.get_users_js()
         c.users_groups_array = repo_model.get_users_groups_js()
 
-    def _get_repo_refs(self, repo, rev=None):
+    def _get_repo_refs(self, repo, rev=None, branch_rev=None):
         """return a structure with repo's interesting changesets, suitable for
         the selectors in pullrequest.html"""
         branches = [('branch:%s:%s' % (k, v), k)
@@ -83,11 +84,25 @@
         tips = [x[1] for x in branches + bookmarks + tags
                 if x[0].endswith(colontip)]
         selected = 'tag:tip:%s' % tip
-        special = [(selected, 'tip (%s)' % ', '.join(tips))]
+        special = [(selected, 'tip: %s' % ', '.join(tips))]
 
         if rev:
             selected = 'rev:%s:%s' % (rev, rev)
-            special.append((selected, rev))
+            special.append((selected, '%s: %s' % (_("Selected"), rev[:12])))
+
+        # list named branches that has been merged to this named branch - it should probably merge back
+        if branch_rev:
+            # not restricting to merge() would also get branch point and be better
+            # (especially because it would get the branch point) ... but is currently too expensive
+            revs = ["sort(parents(branch(id('%s')) and merge()) - branch(id('%s')))" %
+                    (branch_rev, branch_rev)]
+            otherbranches = {}
+            for i in scmutil.revrange(repo._repo, revs):
+                cs = repo.get_changeset(i)
+                otherbranches[cs.branch] = cs.raw_id
+            for branch, node in otherbranches.iteritems():
+                selected = 'branch:%s:%s' % (branch, node)
+                special.append((selected, '%s: %s' % (_('Peer'), branch)))
 
         return [(special, _("Special")),
                 (bookmarks, _("Bookmarks")),
@@ -121,18 +136,23 @@
                     category='warning')
             redirect(url('summary_home', repo_name=org_repo.repo_name))
 
+        org_rev = request.GET.get('rev_end')
+        # rev_start is not directly useful - its parent could however be used
+        # as default for other and thus give a simple compare view
+        #other_rev = request.POST.get('rev_start')
+
         other_repos_info = {}
 
         c.org_repos = []
         c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
         c.default_org_repo = org_repo.repo_name
-        c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance)
+        c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
 
         c.other_repos = []
         # add org repo to other so we can open pull request against itself
         c.other_repos.extend(c.org_repos)
         c.default_other_repo = org_repo.repo_name
-        c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.scm_instance)
+        c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.scm_instance, branch_rev=org_rev)
         usr_data = lambda usr: dict(user_id=usr.user_id,
                                     username=usr.username,
                                     firstname=usr.firstname,
@@ -191,23 +211,12 @@
             return redirect(url('pullrequest_home', repo_name=repo_name))
 
         org_repo = _form['org_repo']
-        org_ref = _form['org_ref']
+        org_ref = 'rev:merge:%s' % _form['merge_rev']
         other_repo = _form['other_repo']
-        other_ref = _form['other_ref']
+        other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
         revisions = _form['revisions']
         reviewers = _form['review_members']
 
-        # if we have cherry picked pull request we don't care what is in
-        # org_ref/other_ref
-        rev_start = request.POST.get('rev_start')
-        rev_end = request.POST.get('rev_end')
-
-        if rev_start and rev_end:
-            # this is swapped to simulate that rev_end is a revision from
-            # parent of the fork
-            org_ref = 'rev:%s:%s' % (rev_end, rev_end)
-            other_ref = 'rev:%s:%s' % (rev_start, rev_start)
-
         title = _form['pullrequest_title']
         description = _form['pullrequest_desc']
 
@@ -265,9 +274,6 @@
         :param pull_request:
         :type pull_request:
         """
-        rev_start = request.GET.get('rev_start')
-        rev_end = request.GET.get('rev_end')
-
         org_repo = pull_request.org_repo
         (org_ref_type,
          org_ref_name,
@@ -279,7 +285,7 @@
          other_ref_rev) = pull_request.other_ref.split(':')
 
         # despite opening revisions for bookmarks/branches/tags, we always
-        # convert this to rev to prevent changes after book or branch change
+        # convert this to rev to prevent changes after bookmark or branch change
         org_ref = ('rev', org_ref_rev)
         other_ref = ('rev', other_ref_rev)
 
@@ -290,10 +296,6 @@
 
         c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
 
-        other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
-                                  if c.cs_ranges[0].parents
-                                  else EmptyChangeset(), 'raw_id'))
-
         c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
 
         c.org_ref = org_ref[1]
@@ -394,6 +396,7 @@
         c.changeset_statuses = ChangesetStatus.STATUSES
 
         c.as_form = False
+        c.ancestor = None # there is one - but right here we don't know which
         return render('/pullrequests/pullrequest_show.html')
 
     @NotAnonymous()
--- a/rhodecode/i18n/en/LC_MESSAGES/rhodecode.po	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/i18n/en/LC_MESSAGES/rhodecode.po	Mon Mar 11 17:09:43 2013 +0100
@@ -3730,7 +3730,7 @@
 msgstr ""
 
 #: rhodecode/templates/files/files_browser.html:52
-msgid "Last commiter"
+msgid "Last committer"
 msgstr ""
 
 #: rhodecode/templates/files/files_edit.html:19
--- a/rhodecode/i18n/fr/LC_MESSAGES/rhodecode.po	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/i18n/fr/LC_MESSAGES/rhodecode.po	Mon Mar 11 17:09:43 2013 +0100
@@ -3869,7 +3869,7 @@
 msgstr "Dernière modification"
 
 #: rhodecode/templates/files/files_browser.html:52
-msgid "Last commiter"
+msgid "Last committer"
 msgstr "Dernier commiteur"
 
 #: rhodecode/templates/files/files_edit.html:19
--- a/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.po	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.po	Mon Mar 11 17:09:43 2013 +0100
@@ -3743,7 +3743,7 @@
 msgstr "最終更新日"
 
 #: rhodecode/templates/files/files_browser.html:52
-msgid "Last commiter"
+msgid "Last committer"
 msgstr "最後の作成者"
 
 #: rhodecode/templates/files/files_edit.html:19
--- a/rhodecode/i18n/pl/LC_MESSAGES/rhodecode.po	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/i18n/pl/LC_MESSAGES/rhodecode.po	Mon Mar 11 17:09:43 2013 +0100
@@ -3836,7 +3836,7 @@
 msgstr "Ostatnio modyfikowany"
 
 #: rhodecode/templates/files/files_browser.html:52
-msgid "Last commiter"
+msgid "Last committer"
 msgstr "Autor"
 
 #: rhodecode/templates/files/files_edit.html:19
--- a/rhodecode/i18n/pt_BR/LC_MESSAGES/rhodecode.po	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/i18n/pt_BR/LC_MESSAGES/rhodecode.po	Mon Mar 11 17:09:43 2013 +0100
@@ -3910,7 +3910,7 @@
 msgstr "Última alteração"
 
 #: rhodecode/templates/files/files_browser.html:52
-msgid "Last commiter"
+msgid "Last committer"
 msgstr "Último commiter"
 
 #: rhodecode/templates/files/files_edit.html:19
--- a/rhodecode/i18n/rhodecode.pot	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/i18n/rhodecode.pot	Mon Mar 11 17:09:43 2013 +0100
@@ -3680,7 +3680,7 @@
 msgstr ""
 
 #: rhodecode/templates/files/files_browser.html:52
-msgid "Last commiter"
+msgid "Last committer"
 msgstr ""
 
 #: rhodecode/templates/files/files_edit.html:19
--- a/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.po	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.po	Mon Mar 11 17:09:43 2013 +0100
@@ -3733,7 +3733,7 @@
 msgstr "最后修改于"
 
 #: rhodecode/templates/files/files_browser.html:52
-msgid "Last commiter"
+msgid "Last committer"
 msgstr "最后提交者"
 
 #: rhodecode/templates/files/files_edit.html:19
--- a/rhodecode/i18n/zh_TW/LC_MESSAGES/rhodecode.po	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/i18n/zh_TW/LC_MESSAGES/rhodecode.po	Mon Mar 11 17:09:43 2013 +0100
@@ -3860,7 +3860,7 @@
 msgstr "最後修改"
 
 #: rhodecode/templates/files/files_browser.html:52
-msgid "Last commiter"
+msgid "Last committer"
 msgstr "最後的遞交者"
 
 #: rhodecode/templates/files/files_edit.html:19
--- a/rhodecode/lib/base.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/base.py	Mon Mar 11 17:09:43 2013 +0100
@@ -43,15 +43,17 @@
 
     ip = environ.get(proxy_key2)
     if ip:
-        # HTTP_X_FORWARDED_FOR can have mutliple ips inside
-        # the left-most being the original client, and each successive proxy
-        # that passed the request adding the IP address where it received the
-        # request from.
-        if ',' in ip:
-            ip = ip.split(',')[0].strip()
         return ip
 
     ip = environ.get(def_key, '0.0.0.0')
+
+    # HEADERS can have mutliple ips inside
+    # the left-most being the original client, and each successive proxy
+    # that passed the request adding the IP address where it received the
+    # request from.
+    if ',' in ip:
+        ip = ip.split(',')[0].strip()
+
     return ip
 
 
@@ -279,7 +281,6 @@
         # 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:
             self.ip_addr = _get_ip_addr(environ)
             # make sure that we update permissions each time we call controller
@@ -300,10 +301,6 @@
             )
             return WSGIController.__call__(self, environ, start_response)
         finally:
-            log.info('IP: %s Request to %s time: %.3fs' % (
-                _get_ip_addr(environ),
-                safe_unicode(_get_access_path(environ)), time.time() - start)
-            )
             meta.Session.remove()
 
 
--- a/rhodecode/lib/celerylib/__init__.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/celerylib/__init__.py	Mon Mar 11 17:09:43 2013 +0100
@@ -59,6 +59,7 @@
 
 
 def run_task(task, *args, **kwargs):
+    global CELERY_ON
     if CELERY_ON:
         try:
             t = task.apply_async(args=args, kwargs=kwargs)
@@ -68,7 +69,6 @@
         except socket.error, e:
             if isinstance(e, IOError) and e.errno == 111:
                 log.debug('Unable to connect to celeryd. Sync execution')
-                global CELERY_ON
                 CELERY_ON = False
             else:
                 log.error(traceback.format_exc())
--- a/rhodecode/lib/exceptions.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/exceptions.py	Mon Mar 11 17:09:43 2013 +0100
@@ -60,12 +60,17 @@
 
 class HTTPLockedRC(HTTPClientError):
     """
-    Special Exception For locked Repos in RhodeCode
+    Special Exception For locked Repos in RhodeCode, the return code can
+    be overwritten by _code keyword argument passed into constructors
     """
     code = 423
     title = explanation = 'Repository Locked'
 
     def __init__(self, reponame, username, *args, **kwargs):
+        from rhodecode import CONFIG
+        from rhodecode.lib.utils2 import safe_int
+        _code = CONFIG.get('lock_ret_code')
+        self.code = safe_int(_code, self.code)
         self.title = self.explanation = ('Repository `%s` locked by '
                                          'user `%s`' % (reponame, username))
         super(HTTPLockedRC, self).__init__(*args, **kwargs)
--- a/rhodecode/lib/helpers.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/helpers.py	Mon Mar 11 17:09:43 2013 +0100
@@ -550,13 +550,18 @@
             return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
                            class_='lazy-cs' if lazy_cs else '')
 
+        def _get_op(rev_txt):
+            _op = None
+            _name = rev_txt
+            if len(rev_txt.split('=>')) == 2:
+                _op, _name = rev_txt.split('=>')
+            return _op, _name
+
         revs = []
         if len(filter(lambda v: v != '', revs_ids)) > 0:
             repo = None
             for rev in revs_ids[:revs_top_limit]:
-                _op = _name = None
-                if len(rev.split('=>')) == 2:
-                    _op, _name = rev.split('=>')
+                _op, _name = _get_op(rev)
 
                 # we want parsed changesets, or new log store format is bad
                 if parse_cs:
@@ -583,6 +588,10 @@
             [lnk(rev, repo_name) for rev in revs[:revs_limit]]
             )
         )
+        _op1, _name1 = _get_op(revs_ids[0])
+        _op2, _name2 = _get_op(revs_ids[-1])
+
+        _rev = '%s...%s' % (_name1, _name2)
 
         compare_view = (
             ' <div class="compare_view tooltip" title="%s">'
@@ -591,7 +600,7 @@
                     revs_ids[0][:12], revs_ids[-1][:12]
                 ),
                 url('changeset_home', repo_name=repo_name,
-                    revision='%s...%s' % (revs_ids[0], revs_ids[-1])
+                    revision=_rev
                 ),
                 _('compare view')
             )
--- a/rhodecode/lib/hooks.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/hooks.py	Mon Mar 11 17:09:43 2013 +0100
@@ -36,7 +36,7 @@
 from rhodecode.lib.vcs.backends.base import EmptyChangeset
 from rhodecode.lib.compat import json
 from rhodecode.lib.exceptions import HTTPLockedRC
-from rhodecode.lib.utils2 import safe_str, datetime_to_time
+from rhodecode.lib.utils2 import safe_str
 from rhodecode.model.db import Repository, User
 
 
@@ -113,7 +113,14 @@
     usr = User.get_by_username(username)
     if locked_by[0] and usr.user_id != int(locked_by[0]):
         locked_by = User.get(locked_by[0]).username
-        raise HTTPLockedRC(repository, locked_by)
+        # this exception is interpreted in git/hg middlewares and based
+        # on that proper return code is server to client
+        _http_ret = HTTPLockedRC(repository, locked_by)
+        if str(_http_ret.code).startswith('2'):
+            #2xx Codes don't raise exceptions
+            sys.stdout.write(_http_ret.title)
+        else:
+            raise _http_ret
 
 
 def pre_pull(ui, repo, **kwargs):
@@ -139,7 +146,14 @@
 
     if locked_by[0]:
         locked_by = User.get(locked_by[0]).username
-        raise HTTPLockedRC(repository, locked_by)
+        # this exception is interpreted in git/hg middlewares and based
+        # on that proper return code is server to client
+        _http_ret = HTTPLockedRC(repository, locked_by)
+        if str(_http_ret.code).startswith('2'):
+            #2xx Codes don't raise exceptions
+            sys.stdout.write(_http_ret.title)
+        else:
+            raise _http_ret
 
 
 def log_pull_action(ui, repo, **kwargs):
@@ -159,12 +173,14 @@
         repository = extras['repository']
         scm = extras['scm']
         make_lock = extras['make_lock']
+        locked_by = extras['locked_by']
         ip = extras['ip']
     elif 'username' in rc_extras:
         username = rc_extras['username']
         repository = rc_extras['repository']
         scm = rc_extras['scm']
         make_lock = rc_extras['make_lock']
+        locked_by = rc_extras['locked_by']
         ip = rc_extras['ip']
     else:
         raise Exception('Missing data in repo.ui and os.environ')
@@ -185,6 +201,12 @@
         #msg = 'Made lock on repo `%s`' % repository
         #sys.stdout.write(msg)
 
+    if locked_by[0]:
+        locked_by = User.get(locked_by[0]).username
+        _http_ret = HTTPLockedRC(repository, locked_by)
+        if str(_http_ret.code).startswith('2'):
+            #2xx Codes don't raise exceptions
+            sys.stdout.write(_http_ret.title)
     return 0
 
 
@@ -207,15 +229,19 @@
         repository = extras['repository']
         scm = extras['scm']
         make_lock = extras['make_lock']
+        locked_by = extras['locked_by']
+        action = extras['action']
     elif 'username' in rc_extras:
         username = rc_extras['username']
         repository = rc_extras['repository']
         scm = rc_extras['scm']
         make_lock = rc_extras['make_lock']
+        locked_by = rc_extras['locked_by']
+        action = extras['action']
     else:
         raise Exception('Missing data in repo.ui and os.environ')
 
-    action = 'push' + ':%s'
+    action = action + ':%s'
 
     if scm == 'hg':
         node = kwargs['node']
@@ -255,6 +281,13 @@
         msg = 'Released lock on repo `%s`\n' % repository
         sys.stdout.write(msg)
 
+    if locked_by[0]:
+        locked_by = User.get(locked_by[0]).username
+        _http_ret = HTTPLockedRC(repository, locked_by)
+        if str(_http_ret.code).startswith('2'):
+            #2xx Codes don't raise exceptions
+            sys.stdout.write(_http_ret.title)
+
     return 0
 
 
--- a/rhodecode/lib/middleware/simplegit.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/middleware/simplegit.py	Mon Mar 11 17:09:43 2013 +0100
@@ -234,7 +234,8 @@
             app = self.__make_app(repo_name, repo_path, extras)
             return app(environ, start_response)
         except HTTPLockedRC, e:
-            log.debug('Repository LOCKED ret code 423!')
+            _code = CONFIG.get('lock_ret_code')
+            log.debug('Repository LOCKED ret code %s!' % (_code))
             return e(environ, start_response)
         except Exception:
             log.error(traceback.format_exc())
--- a/rhodecode/lib/middleware/simplehg.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/middleware/simplehg.py	Mon Mar 11 17:09:43 2013 +0100
@@ -199,7 +199,8 @@
             if str(e).find('not found') != -1:
                 return HTTPNotFound()(environ, start_response)
         except HTTPLockedRC, e:
-            log.debug('Repository LOCKED ret code 423!')
+            _code = CONFIG.get('lock_ret_code')
+            log.debug('Repository LOCKED ret code %s!' % (_code))
             return e(environ, start_response)
         except Exception:
             log.error(traceback.format_exc())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/middleware/wrapper.py	Mon Mar 11 17:09:43 2013 +0100
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.lib.middleware.wrapper
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    request time mesuring app
+
+    :created_on: May 23, 2013
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+import time
+import logging
+from rhodecode.lib.base import _get_ip_addr, _get_access_path
+from rhodecode.lib.utils2 import safe_unicode
+
+
+class RequestWrapper(object):
+
+    def __init__(self, app, config):
+        self.application = app
+        self.config = config
+
+    def __call__(self, environ, start_response):
+        start = time.time()
+        try:
+            return self.application(environ, start_response)
+        finally:
+            log = logging.getLogger('rhodecode.' + self.__class__.__name__)
+            log.info('IP: %s Request to %s time: %.3fs' % (
+                _get_ip_addr(environ),
+                safe_unicode(_get_access_path(environ)), time.time() - start)
+            )
--- a/rhodecode/lib/utils.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/utils.py	Mon Mar 11 17:09:43 2013 +0100
@@ -240,7 +240,7 @@
         return False
 
 
-def is_valid_repos_group(repos_group_name, base_path):
+def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
     """
     Returns True if given path is a repos group False otherwise
 
@@ -263,7 +263,7 @@
         pass
 
     # check if it's a valid path
-    if os.path.isdir(full_path):
+    if skip_path_check or os.path.isdir(full_path):
         return True
 
     return False
@@ -495,7 +495,6 @@
                     #don't hold further removals on error
                     log.error(traceback.format_exc())
                     sa.rollback()
-
     return added, removed
 
 
--- a/rhodecode/lib/utils2.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/utils2.py	Mon Mar 11 17:09:43 2013 +0100
@@ -565,11 +565,15 @@
 
 
 def obfuscate_url_pw(engine):
-    from sqlalchemy.engine import url
-    url = url.make_url(engine)
-    if url.password:
-        url.password = 'XXXXX'
-    return str(url)
+    _url = engine or ''
+    from sqlalchemy.engine import url as sa_url
+    try:
+        _url = sa_url.make_url(engine)
+        if _url.password:
+            _url.password = 'XXXXX'
+    except:
+        pass
+    return str(_url)
 
 
 def get_server_url(environ):
--- a/rhodecode/lib/vcs/backends/base.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/vcs/backends/base.py	Mon Mar 11 17:09:43 2013 +0100
@@ -9,7 +9,7 @@
     :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
 """
 
-
+import datetime
 from itertools import chain
 from rhodecode.lib.vcs.utils import author_name, author_email
 from rhodecode.lib.vcs.utils.lazy import LazyProperty
@@ -311,6 +311,27 @@
         """
         raise NotImplementedError
 
+    def inject_ui(self, **extras):
+        """
+        Injects extra parameters into UI object of this repo
+        """
+        required_extras = [
+            'ip',
+            'username',
+            'action',
+            'repository',
+            'scm',
+            'config',
+            'server_url',
+            'make_lock',
+            'locked_by',
+        ]
+        for req in required_extras:
+            if req not in extras:
+                raise AttributeError('Missing attribute %s in extras' % (req))
+        for k, v in extras.items():
+            self._repo.ui.setconfig('rhodecode_extras', k, v)
+
 
 class BaseChangeset(object):
     """
@@ -433,28 +454,28 @@
         raise NotImplementedError
 
     @LazyProperty
-    def commiter(self):
+    def committer(self):
         """
-        Returns Commiter for given commit
+        Returns Committer for given commit
         """
 
         raise NotImplementedError
 
     @LazyProperty
-    def commiter_name(self):
+    def committer_name(self):
         """
         Returns Author name for given commit
         """
 
-        return author_name(self.commiter)
+        return author_name(self.committer)
 
     @LazyProperty
-    def commiter_email(self):
+    def committer_email(self):
         """
         Returns Author email address for given commit
         """
 
-        return author_email(self.commiter)
+        return author_email(self.committer)
 
     @LazyProperty
     def author(self):
@@ -959,12 +980,12 @@
     """
 
     def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
-                 alias=None, revision=-1, message='', author='', date=''):
+                 alias=None, revision=-1, message='', author='', date=None):
         self._empty_cs = cs
         self.revision = revision
         self.message = message
         self.author = author
-        self.date = date
+        self.date = date or datetime.datetime.fromtimestamp(0)
         self.repository = repo
         self.requested_revision = requested_revision
         self.alias = alias
--- a/rhodecode/lib/vcs/backends/git/changeset.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/vcs/backends/git/changeset.py	Mon Mar 11 17:09:43 2013 +0100
@@ -17,6 +17,7 @@
 from rhodecode.lib.vcs.utils import safe_unicode
 from rhodecode.lib.vcs.utils import date_fromtimestamp
 from rhodecode.lib.vcs.utils.lazy import LazyProperty
+from rhodecode.lib.utils2 import safe_int
 
 
 class GitChangeset(BaseChangeset):
@@ -41,7 +42,7 @@
         self._commit = commit
 
         self._tree_id = commit.tree
-        self._commiter_property = 'committer'
+        self._committer_property = 'committer'
         self._author_property = 'author'
         self._date_property = 'commit_time'
         self._date_tz_property = 'commit_timezone'
@@ -53,8 +54,8 @@
         self._paths = {}
 
     @LazyProperty
-    def commiter(self):
-        return safe_unicode(getattr(self._commit, self._commiter_property))
+    def committer(self):
+        return safe_unicode(getattr(self._commit, self._committer_property))
 
     @LazyProperty
     def author(self):
@@ -275,10 +276,9 @@
         """
         Returns last commit of the file at the given ``path``.
         """
-        node = self.get_node(path)
-        return node.history[0]
+        return self.get_file_history(path, limit=1)[0]
 
-    def get_file_history(self, path):
+    def get_file_history(self, path, limit=None):
         """
         Returns history of file as reversed list of ``Changeset`` objects for
         which file at given ``path`` has been modified.
@@ -287,11 +287,16 @@
         which is generally not good. Should be replaced with algorithm
         iterating commits.
         """
+
         self._get_filectx(path)
-
-        cmd = 'log --pretty="format: %%H" -s -p %s -- "%s"' % (
-                  self.id, path
-               )
+        if limit:
+            cmd = 'log -n %s --pretty="format: %%H" -s -p %s -- "%s"' % (
+                      safe_int(limit, 0), self.id, path
+                   )
+        else:
+            cmd = 'log --pretty="format: %%H" -s -p %s -- "%s"' % (
+                      self.id, path
+                   )
         so, se = self.repository.run_git_command(cmd)
         ids = re.findall(r'[0-9a-fA-F]{40}', so)
         return [self.repository.get_changeset(id) for id in ids]
--- a/rhodecode/lib/vcs/backends/git/repository.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/vcs/backends/git/repository.py	Mon Mar 11 17:09:43 2013 +0100
@@ -67,14 +67,12 @@
     @ThreadLocalLazyProperty
     def _repo(self):
         repo = Repo(self.path)
-        #temporary set that to now at later we will move it to constructor
-        baseui = None
-        if baseui is None:
+        # patch the instance of GitRepo with an "FAKE" ui object to add
+        # compatibility layer with Mercurial
+        if not hasattr(repo, 'ui'):
             from mercurial.ui import ui
             baseui = ui()
-        # patch the instance of GitRepo with an "FAKE" ui object to add
-        # compatibility layer with Mercurial
-        setattr(repo, 'ui', baseui)
+            setattr(repo, 'ui', baseui)
         return repo
 
     @property
@@ -306,6 +304,15 @@
             url = ':///'.join(('file', url))
         return url
 
+    def get_hook_location(self):
+        """
+        returns absolute path to location where hooks are stored
+        """
+        loc = os.path.join(self.path, 'hooks')
+        if not self.bare:
+            loc = os.path.join(self.path, '.git', 'hooks')
+        return loc
+
     @LazyProperty
     def name(self):
         return os.path.basename(self.path)
--- a/rhodecode/lib/vcs/backends/hg/changeset.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/vcs/backends/hg/changeset.py	Mon Mar 11 17:09:43 2013 +0100
@@ -44,8 +44,8 @@
         return safe_unicode(self._ctx.description())
 
     @LazyProperty
-    def commiter(self):
-        return safe_unicode(self.auhtor)
+    def committer(self):
+        return safe_unicode(self.author)
 
     @LazyProperty
     def author(self):
@@ -219,19 +219,23 @@
         """
         Returns last commit of the file at the given ``path``.
         """
-        node = self.get_node(path)
-        return node.history[0]
+        return self.get_file_history(path, limit=1)[0]
 
-    def get_file_history(self, path):
+    def get_file_history(self, path, limit=None):
         """
         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
+        hist = []
+        cnt = 0
+        for cs in reversed([x for x in fctx.filelog()]):
+            cnt += 1
+            hist.append(hex(fctx.filectx(cs).node()))
+            if limit and cnt == limit:
+                break
+
+        return [self.repository.get_changeset(node) for node in hist]
 
     def get_file_annotate(self, path):
         """
--- a/rhodecode/lib/vcs/backends/hg/repository.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/vcs/backends/hg/repository.py	Mon Mar 11 17:09:43 2013 +0100
@@ -422,6 +422,12 @@
             url = "file:" + urllib.pathname2url(url)
         return url
 
+    def get_hook_location(self):
+        """
+        returns absolute path to location where hooks are stored
+        """
+        return os.path.join(self.path, '.hg', '.hgrc')
+
     def get_changeset(self, revision=None):
         """
         Returns ``MercurialChangeset`` object representing repository's
@@ -492,7 +498,7 @@
         """
         return MercurialWorkdir(self)
 
-    def get_config_value(self, section, name, config_file=None):
+    def get_config_value(self, section, name=None, config_file=None):
         """
         Returns configuration value for a given [``section``] and ``name``.
 
--- a/rhodecode/lib/vcs/utils/lazy.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/lib/vcs/utils/lazy.py	Mon Mar 11 17:09:43 2013 +0100
@@ -1,3 +1,14 @@
+class _Missing(object):
+
+    def __repr__(self):
+        return 'no value'
+
+    def __reduce__(self):
+        return '_missing'
+
+_missing = _Missing()
+
+
 class LazyProperty(object):
     """
     Decorator for easier creation of ``property`` from potentially expensive to
@@ -24,8 +35,11 @@
     def __get__(self, obj, klass=None):
         if obj is None:
             return self
-        result = obj.__dict__[self.__name__] = self._func(obj)
-        return result
+        value = obj.__dict__.get(self.__name__, _missing)
+        if value is _missing:
+            value = self._func(obj)
+            obj.__dict__[self.__name__] = value
+        return value
 
 import threading
 
@@ -41,5 +55,8 @@
         if not hasattr(obj, '__tl_dict__'):
             obj.__tl_dict__ = threading.local().__dict__
 
-        result = obj.__tl_dict__[self.__name__] = self._func(obj)
-        return result
+        value = obj.__tl_dict__.get(self.__name__, _missing)
+        if value is _missing:
+            value = self._func(obj)
+            obj.__tl_dict__[self.__name__] = value
+        return value
--- a/rhodecode/model/db.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/model/db.py	Mon Mar 11 17:09:43 2013 +0100
@@ -47,7 +47,7 @@
 from rhodecode.lib.vcs.backends.base import EmptyChangeset
 
 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_suffix, remove_prefix
+    safe_unicode, remove_suffix, remove_prefix, time_to_datetime
 from rhodecode.lib.compat import json
 from rhodecode.lib.caching_query import FromCache
 
@@ -938,15 +938,7 @@
 
     @classmethod
     def inject_ui(cls, repo, extras={}):
-        from rhodecode.lib.vcs.backends.hg import MercurialRepository
-        from rhodecode.lib.vcs.backends.git import GitRepository
-        required = (MercurialRepository, GitRepository)
-        if not isinstance(repo, required):
-            raise Exception('repo must be instance of %s' % required)
-
-        # inject ui extra param to log this action via push logger
-        for k, v in extras.items():
-            repo._repo.ui.setconfig('rhodecode_extras', k, v)
+        repo.inject_ui(extras)
 
     @classmethod
     def is_valid(cls, repo_name):
@@ -980,7 +972,11 @@
             enable_statistics=repo.enable_statistics,
             enable_locking=repo.enable_locking,
             enable_downloads=repo.enable_downloads,
-            last_changeset=repo.changeset_cache
+            last_changeset=repo.changeset_cache,
+            locked_by=User.get(self.locked[0]).get_api_data() \
+                if self.locked[0] else None,
+            locked_date=time_to_datetime(self.locked[1]) \
+                if self.locked[1] else None
         )
         rc_config = RhodeCodeSetting.get_app_settings()
         repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
@@ -1002,6 +998,10 @@
         Session().add(repo)
         Session().commit()
 
+    @classmethod
+    def getlock(cls, repo):
+        return repo.locked
+
     @property
     def last_db_change(self):
         return self.updated_on
@@ -1341,15 +1341,13 @@
 
         return cnt + children_count(self)
 
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
+    def _recursive_objects(self, include_repos=True):
         all_ = []
 
         def _get_members(root_gr):
-            for r in root_gr.repositories:
-                all_.append(r)
+            if include_repos:
+                for r in root_gr.repositories:
+                    all_.append(r)
             childs = root_gr.children.all()
             if childs:
                 for gr in childs:
@@ -1359,6 +1357,18 @@
         _get_members(self)
         return [self] + all_
 
+    def recursive_groups_and_repos(self):
+        """
+        Recursive return all groups, with repositories in those groups
+        """
+        return self._recursive_objects()
+
+    def recursive_groups(self):
+        """
+        Returns all children groups for this group including children of children
+        """
+        return self._recursive_objects(include_repos=False)
+
     def get_new_name(self, group_name):
         """
         returns new full group name based on parent and new name
@@ -1728,7 +1738,7 @@
             for inv_obj in inv_objs:
                 inv_obj.cache_active = False
                 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
-                  % (inv_obj, key, repo_name))
+                  % (inv_obj, key, safe_str(repo_name)))
                 invalidated_keys.append(inv_obj.cache_key)
                 Session().add(inv_obj)
             Session().commit()
--- a/rhodecode/model/forms.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/model/forms.py	Mon Mar 11 17:09:43 2013 +0100
@@ -395,4 +395,7 @@
         pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
         pullrequest_desc = v.UnicodeString(strip=True, required=False)
 
+        ancestor_rev = v.UnicodeString(strip=True, required=True)
+        merge_rev = v.UnicodeString(strip=True, required=True)
+
     return _PullRequestForm
--- a/rhodecode/model/pull_request.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/model/pull_request.py	Mon Mar 11 17:09:43 2013 +0100
@@ -161,7 +161,7 @@
         pull_request.updated_on = datetime.datetime.now()
         Session().add(pull_request)
 
-    def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref):
+    def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
         """
         Returns a list of changesets that can be merged from org_repo@org_ref
         to other_repo@other_ref ... and the ancestor that would be used for merge
@@ -211,16 +211,21 @@
             else:
                 hgrepo = other_repo._repo
 
-            revs = ["ancestors(id('%s')) and not ancestors(id('%s'))" %
-                    (other_rev, org_rev)]
-            changesets = [other_repo.get_changeset(cs)
-                          for cs in scmutil.revrange(hgrepo, revs)]
+            if merge:
+                revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
+                        (other_rev, org_rev, org_rev)]
 
-            if org_repo != other_repo:
                 ancestors = scmutil.revrange(hgrepo,
                      ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
                 if len(ancestors) == 1:
                     ancestor = hgrepo[ancestors[0]].hex()
+            else:
+                # TODO: have both + and - changesets
+                revs = ["id('%s') :: id('%s') - id('%s')" %
+                        (org_rev, other_rev, org_rev)]
+
+            changesets = [other_repo.get_changeset(cs)
+                          for cs in scmutil.revrange(hgrepo, revs)]
 
         elif alias == 'git':
             assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
@@ -233,7 +238,7 @@
 
         return changesets, ancestor
 
-    def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
+    def get_compare_data(self, org_repo, org_ref, other_repo, other_ref, merge):
         """
         Returns incoming changesets for mercurial repositories
 
@@ -251,5 +256,6 @@
 
         cs_ranges, ancestor = self._get_changesets(org_repo.scm_instance.alias,
                                                    org_repo.scm_instance, org_ref,
-                                                   other_repo.scm_instance, other_ref)
+                                                   other_repo.scm_instance, other_ref,
+                                                   merge)
         return cs_ranges, ancestor
--- a/rhodecode/model/repo.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/model/repo.py	Mon Mar 11 17:09:43 2013 +0100
@@ -32,7 +32,7 @@
 from rhodecode.lib.vcs.backends import get_backend
 from rhodecode.lib.compat import json
 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode,\
-    remove_prefix
+    remove_prefix, obfuscate_url_pw
 from rhodecode.lib.caching_query import FromCache
 from rhodecode.lib.hooks import log_create_repository, log_delete_repository
 
@@ -42,8 +42,6 @@
     RhodeCodeSetting, RepositoryField
 from rhodecode.lib import helpers as h
 from rhodecode.lib.auth import HasRepoPermissionAny
-from rhodecode.lib.vcs.backends.base import EmptyChangeset
-
 
 log = logging.getLogger(__name__)
 
@@ -640,7 +638,8 @@
             raise Exception('This path %s is a valid group' % repo_path)
 
         log.info('creating repo %s in %s @ %s' % (
-                     repo_name, safe_unicode(repo_path), clone_uri
+                     repo_name, safe_unicode(repo_path),
+                     obfuscate_url_pw(clone_uri)
                 )
         )
         backend = get_backend(alias)
--- a/rhodecode/model/repos_group.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/model/repos_group.py	Mon Mar 11 17:09:43 2013 +0100
@@ -249,27 +249,37 @@
 
             # change properties
             repos_group.group_description = form_data['group_description']
-            repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
             repos_group.group_parent_id = form_data['group_parent_id']
             repos_group.enable_locking = form_data['enable_locking']
+
+            repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
             repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
             new_path = repos_group.full_path
-
             self.sa.add(repos_group)
 
-            # iterate over all members of this groups and set the locking !
+            # iterate over all members of this groups and do fixes
+            # set locking if given
+            # if obj is a repoGroup also fix the name of the group according
+            # to the parent
+            # if obj is a Repo fix it's name
             # this can be potentially heavy operation
             for obj in repos_group.recursive_groups_and_repos():
                 #set the value from it's parent
                 obj.enable_locking = repos_group.enable_locking
+                if isinstance(obj, RepoGroup):
+                    new_name = obj.get_new_name(obj.name)
+                    log.debug('Fixing group %s to new name %s' \
+                                % (obj.group_name, new_name))
+                    obj.group_name = new_name
+                elif isinstance(obj, Repository):
+                    # we need to get all repositories from this new group and
+                    # rename them accordingly to new group path
+                    new_name = obj.get_new_name(obj.just_name)
+                    log.debug('Fixing repo %s to new name %s' \
+                                % (obj.repo_name, new_name))
+                    obj.repo_name = new_name
                 self.sa.add(obj)
 
-            # 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.__rename_group(old_path, new_path)
 
             return repos_group
--- a/rhodecode/model/scm.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/model/scm.py	Mon Mar 11 17:09:43 2013 +0100
@@ -44,13 +44,14 @@
 
 from rhodecode import BACKENDS
 from rhodecode.lib import helpers as h
-from rhodecode.lib.utils2 import safe_str, safe_unicode
+from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url
 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
     action_logger, REMOVED_REPO_PAT
 from rhodecode.model import BaseModel
 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
     UserFollowing, UserLog, User, RepoGroup, PullRequest
+from rhodecode.lib.hooks import log_push_action
 
 log = logging.getLogger(__name__)
 
@@ -402,6 +403,60 @@
         self.sa.add(repo)
         return repo
 
+    def _handle_push(self, repo, username, action, repo_name, revisions):
+        """
+        Triggers push action hooks
+
+        :param repo: SCM repo
+        :param username: username who pushes
+        :param action: push/push_loca/push_remote
+        :param repo_name: name of repo
+        :param revisions: list of revisions that we pushed
+        """
+        from rhodecode import CONFIG
+        from rhodecode.lib.base import _get_ip_addr
+        try:
+            from pylons import request
+            environ = request.environ
+        except TypeError:
+            # we might use this outside of request context, let's fake the
+            # environ data
+            from webob import Request
+            environ = Request.blank('').environ
+
+        #trigger push hook
+        extras = {
+            'ip': _get_ip_addr(environ),
+            'username': username,
+            'action': 'push_local',
+            'repository': repo_name,
+            'scm': repo.alias,
+            'config': CONFIG['__file__'],
+            'server_url': get_server_url(environ),
+            'make_lock': None,
+            'locked_by': [None, None]
+        }
+        _scm_repo = repo._repo
+        repo.inject_ui(**extras)
+        if repo.alias == 'hg':
+            log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
+        elif repo.alias == 'git':
+            log_push_action(_scm_repo.ui, _scm_repo, _git_revs=revisions)
+
+    def _get_IMC_module(self, scm_type):
+        """
+        Returns InMemoryCommit class based on scm_type
+
+        :param scm_type:
+        """
+        if scm_type == 'hg':
+            from rhodecode.lib.vcs.backends.hg import \
+                MercurialInMemoryChangeset as IMC
+        elif scm_type == 'git':
+            from rhodecode.lib.vcs.backends.git import \
+                GitInMemoryChangeset as IMC
+        return IMC
+
     def pull_changes(self, repo, username):
         dbrepo = self.__get_repo(repo)
         clone_uri = dbrepo.clone_uri
@@ -409,26 +464,13 @@
             raise Exception("This repository doesn't have a clone uri")
 
         repo = dbrepo.scm_instance
-        from rhodecode import CONFIG
+        repo_name = dbrepo.repo_name
         try:
-            extras = {
-                'ip': '',
-                'username': username,
-                'action': 'push_remote',
-                'repository': dbrepo.repo_name,
-                'scm': repo.alias,
-                'config': CONFIG['__file__'],
-                'make_lock': None,
-                'locked_by': [None, None]
-            }
-
-            Repository.inject_ui(repo, extras=extras)
-
             if repo.alias == 'git':
                 repo.fetch(clone_uri)
             else:
                 repo.pull(clone_uri)
-            self.mark_for_invalidation(dbrepo.repo_name)
+            self.mark_for_invalidation(repo_name)
         except:
             log.error(traceback.format_exc())
             raise
@@ -441,13 +483,8 @@
         :param repo: SCM instance
 
         """
-
-        if repo.alias == 'hg':
-            from rhodecode.lib.vcs.backends.hg import \
-                MercurialInMemoryChangeset as IMC
-        elif repo.alias == 'git':
-            from rhodecode.lib.vcs.backends.git import \
-                GitInMemoryChangeset as IMC
+        user = self._get_user(user)
+        IMC = self._get_IMC_module(repo.alias)
 
         # decoding here will force that we have proper encoded values
         # in any other case this will throw exceptions and deny commit
@@ -463,20 +500,21 @@
                        author=author,
                        parents=[cs], branch=cs.branch)
 
-        action = 'push_local:%s' % tip.raw_id
-        action_logger(user, action, repo_name)
         self.mark_for_invalidation(repo_name)
+        self._handle_push(repo,
+                          username=user.username,
+                          action='push_local',
+                          repo_name=repo_name,
+                          revisions=[tip.raw_id])
         return tip
 
     def create_node(self, repo, repo_name, cs, user, author, message, content,
                       f_path):
-        if repo.alias == 'hg':
-            from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
-        elif repo.alias == 'git':
-            from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
+        user = self._get_user(user)
+        IMC = self._get_IMC_module(repo.alias)
+
         # decoding here will force that we have proper encoded values
         # in any other case this will throw exceptions and deny commit
-
         if isinstance(content, (basestring,)):
             content = safe_str(content)
         elif isinstance(content, (file, cStringIO.OutputType,)):
@@ -502,9 +540,12 @@
                        author=author,
                        parents=parents, branch=cs.branch)
 
-        action = 'push_local:%s' % tip.raw_id
-        action_logger(user, action, repo_name)
         self.mark_for_invalidation(repo_name)
+        self._handle_push(repo,
+                          username=user.username,
+                          action='push_local',
+                          repo_name=repo_name,
+                          revisions=[tip.raw_id])
         return tip
 
     def get_nodes(self, repo_name, revision, root_path='/', flat=True):
--- a/rhodecode/model/validators.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/model/validators.py	Mon Mar 11 17:09:43 2013 +0100
@@ -416,6 +416,8 @@
                 svnremoterepo(ui, url).capabilities
             elif url.startswith('git+http'):
                 raise NotImplementedError()
+            else:
+                raise Exception('clone from URI %s not allowed' % (url))
 
         elif repo_type == 'git':
             from rhodecode.lib.vcs.backends.git.repository import GitRepository
@@ -427,6 +429,8 @@
                 raise NotImplementedError()
             elif url.startswith('hg+http'):
                 raise NotImplementedError()
+            else:
+                raise Exception('clone from URI %s not allowed' % (url))
 
     class _validator(formencode.validators.FancyValidator):
         messages = {
--- a/rhodecode/public/css/codemirror.css	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/public/css/codemirror.css	Mon Mar 11 17:09:43 2013 +0100
@@ -171,4 +171,4 @@
     visibility: hidden;
   }
 
-}
+}
\ No newline at end of file
--- a/rhodecode/public/css/pygments.css	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/public/css/pygments.css	Mon Mar 11 17:09:43 2013 +0100
@@ -14,7 +14,7 @@
 div.codeblock .code-header {
     border-bottom: 1px solid #CCCCCC;
     background: #EEEEEE;
-    padding:10px 0 10px 0;
+    padding: 10px 0 10px 0;
 }
 
 div.codeblock .code-header .stats {
@@ -26,38 +26,38 @@
 }
 
 div.codeblock .code-header .stats .left {
-    float:left;
+    float: left;
 }
 div.codeblock .code-header .stats .left.img {
-    margin-top:-2px;
+    margin-top: -2px;
 }
 div.codeblock .code-header .stats .left.item {
-    float:left;
+    float: left;
     padding: 0 9px 0 9px;
-    border-right:1px solid #ccc;
+    border-right: 1px solid #ccc;
 }
 div.codeblock .code-header .stats .left.item pre {
 }
 div.codeblock .code-header .stats .left.item.last {
-    border-right:none;
+    border-right: none;
 }
 div.codeblock .code-header .stats .buttons {
-    float:right;
-    padding-right:4px;
+    float: right;
+    padding-right: 4px;
 }
 
 div.codeblock .code-header .author {
-    margin-left:25px;
+    margin-left: 25px;
     font-weight: bold;
     height: 25px;
 }
 div.codeblock .code-header .author .user {
-    padding-top:3px;
+    padding-top: 3px;
 }
 div.codeblock .code-header .commit {
-    margin-left:25px;
+    margin-left: 25px;
     font-weight: normal;
-    white-space:pre;
+    white-space: pre;
 }
 
 div.codeblock .code-body table {
@@ -90,8 +90,8 @@
     display: block;
 }
 div.annotatediv {
-    margin-left:2px;
-    margin-right:4px;
+    margin-left: 2px;
+    margin-right: 4px;
 }
 .code-highlight {
     padding: 0px;
@@ -170,4 +170,4 @@
 .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 */
+.code-highlight .il, .codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
\ No newline at end of file
--- a/rhodecode/public/css/style.css	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/public/css/style.css	Mon Mar 11 17:09:43 2013 +0100
@@ -4143,11 +4143,11 @@
     font-weight: normal;
 }
 
-div.rst-block  {
+div.rst-block {
     background-color: #fafafa;
 }
 
-div.rst-block  {
+div.rst-block {
     clear: both;
     overflow: hidden;
     margin: 0;
@@ -4420,7 +4420,7 @@
     padding: 10px 20px;
 }
 
-.inline-comments div.rst-block  {
+.inline-comments div.rst-block {
     clear: both;
     overflow: hidden;
     margin: 0;
@@ -4811,4 +4811,4 @@
 .lineno:target a {
     border: solid 2px #ee0 !important;
     margin: -2px;
-}
+}
\ No newline at end of file
--- a/rhodecode/public/js/graph.js	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/public/js/graph.js	Mon Mar 11 17:09:43 2013 +0100
@@ -26,7 +26,7 @@
 	
 	this.canvas = document.getElementById("graph_canvas");
 	
-	if (!document.createElement("canvas").getContext) 
+	if (!document.createElement("canvas").getContext)
 		this.canvas = window.G_vmlCanvasManager.initElement(this.canvas);
 	this.ctx = this.canvas.getContext('2d');
 	this.ctx.strokeStyle = 'rgb(0, 0, 0)';
--- a/rhodecode/public/js/rhodecode.js	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/public/js/rhodecode.js	Mon Mar 11 17:09:43 2013 +0100
@@ -86,19 +86,6 @@
     }
 }
 
-var setSelectValue = function(select, val){
-	var selection =  YUD.get(select);
-	
-    // select element
-    for(var i=0;i<selection.options.length;i++){
-        if (selection.options[i].innerHTML == val) {
-            selection.selectedIndex = i;
-            break;
-        }
-    }	
-}
-
-
 /**
  * SmartColorGenerator
  *
@@ -577,7 +564,7 @@
     var args= 'auth_token='+token;
     
     if(!YUD.hasClass(target, 'loaded')){
-        YUD.get(target).innerHTML = _TM['loading...'];
+        YUD.get(target).innerHTML = _TM['Loading ...'];
         var url = pyroutes.url('repo_size', {"repo_name":repo_name});
         YUC.asyncRequest('POST',url,{
             success:function(o){
@@ -910,7 +897,7 @@
 
 var createInlineAddButton = function(tr){
 
-	var label = TRANSLATION_MAP['add another comment'];
+	var label = TRANSLATION_MAP['Add another comment'];
 	
 	var html_el = document.createElement('div');
 	YUD.addClass(html_el, 'add-comment');
@@ -1106,7 +1093,7 @@
 	                    match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,new_url,n_hl));
 	                }
 	                if(match.length >= matches_max){
-	                    match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['search truncated']));
+	                    match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['Search truncated']));
 	                }
 	            }                       
 	        }
@@ -1115,7 +1102,7 @@
 	            YUD.setStyle('tbody_filtered','display','');
 	            
 	            if (match.length==0){
-	              match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['no matching files']));
+	              match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['No matching files']));
 	            }                           
 	            
 	            YUD.get('tbody_filtered').innerHTML = match.join("");   
@@ -2173,11 +2160,11 @@
 	    console.log(t);
 		if(YUD.hasClass(t, 'hidden')){
 			YUD.removeClass(t, 'hidden');
-			YUD.get(button).innerHTML = "&uarr; {0} &uarr;".format(_TM['collapse diff']);
+			YUD.get(button).innerHTML = "&uarr; {0} &uarr;".format(_TM['Collapse diff']);
 		}
 		else if(!YUD.hasClass(t, 'hidden')){
 			YUD.addClass(t, 'hidden');
-			YUD.get(button).innerHTML = "&darr; {0} &darr;".format(_TM['expand diff']);
+			YUD.get(button).innerHTML = "&darr; {0} &darr;".format(_TM['Expand diff']);
 		}
 	});
 	
--- a/rhodecode/templates/admin/users/user_edit.html	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/templates/admin/users/user_edit.html	Mon Mar 11 17:09:43 2013 +0100
@@ -43,11 +43,14 @@
                 <label>${_('API key')}:</label> ${c.user.api_key}
             </div>
         </div>
+        ##show current ip just if we show ourself
+        %if c.rhodecode_user.username == c.user.username:
         <div class="field">
             <div class="label">
                 <label>${_('Current IP')}:</label> ${c.perm_user.ip_addr or "?"}
             </div>
         </div>
+        %endif
         <div class="fields">
              <div class="field">
                 <div class="label">
--- a/rhodecode/templates/admin/users/user_edit_my_account_form.html	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/templates/admin/users/user_edit_my_account_form.html	Mon Mar 11 17:09:43 2013 +0100
@@ -20,6 +20,11 @@
                     <label>${_('API key')}</label> ${c.user.api_key}
                 </div>
             </div>
+            <div class="field">
+                <div class="label">
+                    <label>${_('Current IP')}:</label> ${c.perm_user.ip_addr or "?"}
+                </div>
+            </div>
             <div class="fields">
                  <div class="field">
                     <div class="label">
--- a/rhodecode/templates/base/root.html	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/templates/base/root.html	Mon Mar 11 17:09:43 2013 +0100
@@ -41,21 +41,21 @@
             <script type="text/javascript">
             //JS translations map
             var TRANSLATION_MAP = {
-                'add another comment':'${_("add another comment")}',
+                'Add another comment':'${_("Add another comment")}',
                 'Stop following this repository':"${_('Stop following this repository')}",
                 'Start following this repository':"${_('Start following this repository')}",
                 'Group':"${_('Group')}",
                 'members':"${_('members')}",
-                'loading...':"${_('loading...')}",
-                'search truncated': "${_('search truncated')}",
-                'no matching files': "${_('no matching files')}",
+                'Loading ...':"${_('Loading ...')}",
+                'Search truncated': "${_('Search truncated')}",
+                'No matching files': "${_('No matching files')}",
                 'Open new pull request': "${_('Open new pull request')}",
                 'Open new pull request for selected changesets':  "${_('Open new pull request for selected changesets')}",
                 'Show selected changes __S -> __E': "${_('Show selected changes __S -> __E')}",
                 'Show selected change __S': "${_('Show selected change __S')}",
                 'Selection link': "${_('Selection link')}",
-                'collapse diff': "${_('collapse diff')}",
-                'expand diff': "${_('expand diff')}",
+                'Collapse diff': "${_('Collapse diff')}",
+                'Expand diff': "${_('Expand diff')}"
             };
             var _TM = TRANSLATION_MAP;
 
--- a/rhodecode/templates/changelog/changelog.html	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/templates/changelog/changelog.html	Mon Mar 11 17:09:43 2013 +0100
@@ -25,15 +25,15 @@
     <div class="table">
         % if c.pagination:
             <div id="graph">
-                <div class="info_box" style="clear: both;padding: 10px 6px;text-align: right;">
+                    <div class="info_box" style="clear: both;padding: 10px 6px;min-height: 12px;text-align: right;">
                     <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
                     <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
 
                     %if c.rhodecode_db_repo.fork:
-                        <a title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default')}" class="ui-btn small">${_('Compare fork with parent')}</a>
+                        <a id="compare_fork" title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default',merge=1)}" class="ui-btn small">${_('Compare fork with parent')}</a>
                     %endif
                     %if h.is_hg(c.rhodecode_repo):
-                    <a id="open_new_pr" href="${h.url('pullrequest_form',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
+                    <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
                     %endif
                 </div>
                 <div class="container_header">
@@ -125,8 +125,7 @@
                 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
                 var pr_tmpl = "${h.url('pullrequest_home',repo_name=c.repo_name)}";
 
-                var checkbox_checker = function(e){
-                    var clicked_cb = e.currentTarget;
+                    var checkbox_checker = function(e){
                     var checked_checkboxes = [];
                     for (pos in checkboxes){
                         if(checkboxes[pos].checked){
@@ -134,13 +133,17 @@
                         }
                     }
                     if(YUD.get('open_new_pr')){
-                        if(checked_checkboxes.length>0){
-                            // modify open pull request to show we have selected cs
-                            YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
-                        }else{
-                            YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
+                            if(checked_checkboxes.length>1){
+                              YUD.setStyle('open_new_pr','display','none');
+                            } else {
+                               YUD.setStyle('open_new_pr','display','');
+                            if(checked_checkboxes.length>0){
+                              YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
+                            }else{
+                              YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
+                            }
+                          }
                         }
-                    }
 
                     if(checked_checkboxes.length>0){
                         var rev_end = checked_checkboxes[0].name;
@@ -160,20 +163,25 @@
                         YUD.setStyle('rev_range_clear','display','');
 
                         YUD.get('open_new_pr').href = pr_tmpl + '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end);
-
+                            YUD.setStyle('compare_fork','display','none');
                     } else{
                         YUD.setStyle('rev_range_container','display','none');
                         YUD.setStyle('rev_range_clear','display','none');
-                    }
-                };
-                YUE.onDOMReady(checkbox_checker);
-                YUE.on(checkboxes,'click', checkbox_checker);
+                            if (checkboxes){
+                                YUD.get('open_new_pr').href = pr_tmpl + '?rev_end={0}'.format(checkboxes[0].name);
+                            }
+                            YUD.setStyle('compare_fork','display','');
+                        }
+                    };
+                    YUE.onDOMReady(checkbox_checker);
+                    YUE.on(checkboxes,'click', checkbox_checker);
 
                 YUE.on('rev_range_clear','click',function(e){
                     for (var i=0; i<checkboxes.length; i++){
                         var cb = checkboxes[i];
                         cb.checked = false;
                     }
+                        checkbox_checker();
                     YUE.preventDefault(e);
                 });
 
--- a/rhodecode/templates/changeset/diff_block.html	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/templates/changeset/diff_block.html	Mon Mar 11 17:09:43 2013 +0100
@@ -52,7 +52,7 @@
           <div class="changeset_header">
               <div class="changeset_file">
                   ${h.safe_unicode(filenode_path)} |
-                  <a class="spantag" href="${h.url('files_home', repo_name=c.repo_name, f_path=filenode_path, revision=c.org_ref)}" title="${_('show file at latest version in this repo')}">${c.org_ref_type}@${h.short_id(c.org_ref) if c.org_ref_type=='rev' else c.org_ref}</a> -&gt;
+                  <a class="spantag" href="${h.url('files_home', repo_name=c.other_repo.repo_name, f_path=filenode_path, revision=c.org_ref)}" title="${_('show file at latest version in this repo')}">${c.org_ref_type}@${h.short_id(c.org_ref) if c.org_ref_type=='rev' else c.org_ref}</a> -&gt;
                   <a class="spantag" href="${h.url('files_home', repo_name=c.repo_name, f_path=filenode_path, revision=c.other_ref)}" title="${_('show file at initial version in this repo')}">${c.other_ref_type}@${h.short_id(c.other_ref) if c.other_ref_type=='rev' else c.other_ref}</a>
               </div>
           </div>
--- a/rhodecode/templates/compare/compare_cs.html	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/templates/compare/compare_cs.html	Mon Mar 11 17:09:43 2013 +0100
@@ -1,10 +1,10 @@
 ## Changesets table !
 <div class="container">
-  <table class="compare_view_commits noborder">
   %if not c.cs_ranges:
     <span class="empty_data">${_('No changesets')}</span>
   %else:
-    %for cnt, cs in enumerate(c.cs_ranges):
+    <table class="compare_view_commits noborder">
+    %for cs in reversed(c.cs_ranges):
         <tr>
         <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),14)}"/></div></td>
         <td>
@@ -22,7 +22,15 @@
         <td><div class="message tooltip" title="${h.tooltip(cs.message)}" style="white-space:normal">${h.urlify_commit(h.shorter(cs.message, 60),c.repo_name)}</div></td>
         </tr>
     %endfor
-
+    </table>
+    %if c.ancestor:
+    <span class="ancestor">${_('Ancestor')}:
+      ${h.link_to(h.short_id(c.ancestor),h.url('changeset_home',repo_name=c.repo_name,revision=c.ancestor))}
+    </span>
+    %endif
+    %if c.as_form:
+      ${h.hidden('ancestor_rev',c.ancestor)}
+      ${h.hidden('merge_rev',c.cs_ranges[-1].raw_id)}
+    %endif
   %endif
-  </table>
 </div>
--- a/rhodecode/templates/email_templates/pull_request.html	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/templates/email_templates/pull_request.html	Mon Mar 11 17:09:43 2013 +0100
@@ -10,10 +10,9 @@
 </p>
 
 <div>${_('revisions for reviewing')}</div>
-<pre>
+<p style="white-space: pre-wrap;">
 %for r,r_msg in pr_revisions:
-${h.short_id(r)}:
-    ${h.shorter(r_msg, 256)}
-
+<b>${h.short_id(r)}</b>:
+${h.shorter(r_msg, 256)}
 %endfor
-</pre>
+</p>
--- a/rhodecode/templates/files/files_browser.html	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/templates/files/files_browser.html	Mon Mar 11 17:09:43 2013 +0100
@@ -49,7 +49,7 @@
                     <th>${_('Mimetype')}</th>
                     <th>${_('Last Revision')}</th>
                     <th>${_('Last modified')}</th>
-                    <th>${_('Last commiter')}</th>
+                    <th>${_('Last committer')}</th>
                 </tr>
             </thead>
 
@@ -89,8 +89,8 @@
                      <td>
                          %if node.is_file():
                              <div class="tooltip" title="${h.tooltip(node.last_changeset.message)}">
-                             <pre>${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}</pre>
-                            </div>
+                              <pre>${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}</pre>
+                             </div>
                          %endif
                      </td>
                      <td>
--- a/rhodecode/templates/pullrequests/pullrequest.html	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/templates/pullrequests/pullrequest.html	Mon Mar 11 17:09:43 2013 +0100
@@ -21,9 +21,6 @@
     </div>
     ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
     <div style="float:left;padding:0px 30px 30px 30px">
-        <input type="hidden" name="rev_start" value="${request.GET.get('rev_start')}" />
-        <input type="hidden" name="rev_end" value="${request.GET.get('rev_end')}" />
-
         ##ORG
         <div style="float:left">
             <div>
@@ -101,7 +98,7 @@
 
             <div class="field">
                 <div class="label label-textarea">
-                    <label for="pullrequest_desc">${_('description')}:</label>
+                    <label for="pullrequest_desc">${_('Description')}:</label>
                 </div>
                 <div class="textarea text-area editor">
                     ${h.textarea('pullrequest_desc',size=30)}
@@ -125,8 +122,31 @@
 
   var other_repos_info = ${c.other_repos_info|n};
 
+  var otherrepoChanged = function(){
+      var sel_box = YUQ('#pull_request_form #other_repo')[0];
+      var repo_name = sel_box.options[sel_box.selectedIndex].value;
+
+      YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
+      // replace options of other_ref with the ones for the current other_repo
+      var other_ref_selector = YUD.get('other_ref');
+      var new_select = YUD.createElementFromMarkup(other_repos_info[repo_name]['revs']);
+      var new_selectedIndex = new_select.selectedIndex;
+      other_ref_selector.innerHTML = ""; // clear old options
+      while (new_select.length > 0){ // children will be popped when appened to other_ref_selector
+          other_ref_selector.appendChild(new_select.children[0]);
+      }
+      // browsers lost track of selected when appendChild was used
+      other_ref_selector.selectedIndex = new_selectedIndex;
+
+      // reset && add the reviewer based on selected repo
+      var _data = other_repos_info[repo_name];
+      YUD.get('review_members').innerHTML = '';
+      addReviewMember(_data.user.user_id, _data.user.firstname,
+                      _data.user.lastname, _data.user.username,
+                      _data.user.gravatar_link);
+  }
+
   var loadPreview = function(){
-      YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
       //url template
       var url = "${h.url('compare_url',
                          repo_name='__other_repo__',
@@ -136,8 +156,8 @@
                          other_ref_type='__org_ref_type__',
                          other_ref='__org_ref__',
                          as_form=True,
-                         rev_start=request.GET.get('rev_start',''),
-                         rev_end=request.GET.get('rev_end',''))}";
+                         merge=True,
+                         )}";
       var org_repo = YUQ('#pull_request_form #org_repo')[0].value;
       var org_ref = YUQ('#pull_request_form #org_ref')[0].value.split(':');
 
@@ -159,22 +179,10 @@
       }
 
       YUD.get('pull_request_overview').innerHTML = "${_('Loading ...')}";
+      ypjax(url,'pull_request_overview');
+
       YUD.get('pull_request_overview_url').href = url; // shouldn't have as_form ... but ...
       YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
-      ypjax(url,'pull_request_overview', function(data){
-          var sel_box = YUQ('#pull_request_form #other_repo')[0];
-          var repo_name = sel_box.options[sel_box.selectedIndex].value;
-          var _data = other_repos_info[repo_name];
-          YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
-          YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
-          // select back the revision that was just compared
-          setSelectValue(YUD.get('other_ref'), rev_data['other_ref']);
-          // reset && add the reviewer based on selected repo
-          YUD.get('review_members').innerHTML = '';
-          addReviewMember(_data.user.user_id, _data.user.firstname,
-                          _data.user.lastname, _data.user.username,
-                          _data.user.gravatar_link);
-      })
   }
 
   ## refresh automatically when something changes (org_repo can't change)
@@ -184,9 +192,7 @@
   });
 
   YUE.on('other_repo', 'change', function(e){
-      var repo_name = e.currentTarget.value;
-      // replace the <select> of changed repo
-      YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
+      otherrepoChanged();
       loadPreview();
   });
 
@@ -194,8 +200,9 @@
      loadPreview();
   });
 
+  otherrepoChanged();
   //lazy load overview after 0.5s
-  setTimeout(loadPreview, 500)
+  setTimeout(loadPreview, 500);
 
 </script>
 
--- a/rhodecode/templates/pullrequests/pullrequest_show.html	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/templates/pullrequests/pullrequest_show.html	Mon Mar 11 17:09:43 2013 +0100
@@ -161,7 +161,7 @@
                <div id="reviewers_container"></div>
             </div>
             <div style="padding:0px 10px">
-             <span id="update_pull_request" class="ui-btn xsmall">${_('save changes')}</span>
+             <span id="update_pull_request" class="ui-btn xsmall">${_('Save changes')}</span>
             </div>
             %endif
           </div>
--- a/rhodecode/tests/__init__.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/tests/__init__.py	Mon Mar 11 17:09:43 2013 +0100
@@ -47,7 +47,8 @@
     'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
     'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
     'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'HG_REMOTE_REPO',
-    'GIT_REMOTE_REPO', 'SCM_TESTS', '_get_repo_create_params'
+    'GIT_REMOTE_REPO', 'SCM_TESTS', '_get_repo_create_params',
+    '_get_group_create_params'
 ]
 
 # Invoke websetup with the current config file
@@ -183,3 +184,18 @@
         defs.update({'repo_name_full': defs['repo_name']})
 
     return defs
+
+
+def _get_group_create_params(**custom):
+    defs = dict(
+        group_name=None,
+        group_description='DESC',
+        group_parent_id=None,
+        perms_updates=[],
+        perms_new=[],
+        enable_locking=False,
+        recursive=False
+    )
+    defs.update(custom)
+
+    return defs
--- a/rhodecode/tests/api/api_base.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/tests/api/api_base.py	Mon Mar 11 17:09:43 2013 +0100
@@ -370,6 +370,17 @@
                    % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
         self._compare_ok(id_, expected, given=response.body)
 
+    def test_api_lock_repo_lock_optional_locked(self):
+        from rhodecode.lib.utils2 import  time_to_datetime
+        _locked_since = json.dumps(time_to_datetime(Repository\
+                                    .get_by_repo_name(self.REPO).locked[1]))
+        id_, params = _build_data(self.apikey, 'lock',
+                                  repoid=self.REPO)
+        response = api_call(self, params)
+        expected = ('Repo `%s` locked by `%s`. Locked=`True`. Locked since: `%s`'
+                   % (self.REPO, TEST_USER_ADMIN_LOGIN, _locked_since))
+        self._compare_ok(id_, expected, given=response.body)
+
     @mock.patch.object(Repository, 'lock', crash)
     def test_api_lock_error(self):
         id_, params = _build_data(self.apikey, 'lock',
@@ -381,6 +392,32 @@
         expected = 'Error occurred locking repository `%s`' % self.REPO
         self._compare_error(id_, expected, given=response.body)
 
+    def test_api_get_locks_regular_user(self):
+        id_, params = _build_data(self.apikey_regular, 'get_locks')
+        response = api_call(self, params)
+        expected = []
+        self._compare_ok(id_, expected, given=response.body)
+
+    def test_api_get_locks_with_userid_regular_user(self):
+        id_, params = _build_data(self.apikey_regular, 'get_locks',
+                                  userid=TEST_USER_ADMIN_LOGIN)
+        response = api_call(self, params)
+        expected = 'userid is not the same as your user'
+        self._compare_error(id_, expected, given=response.body)
+
+    def test_api_get_locks(self):
+        id_, params = _build_data(self.apikey, 'get_locks')
+        response = api_call(self, params)
+        expected = []
+        self._compare_ok(id_, expected, given=response.body)
+
+    def test_api_get_locks_with_userid(self):
+        id_, params = _build_data(self.apikey, 'get_locks',
+                                  userid=TEST_USER_REGULAR_LOGIN)
+        response = api_call(self, params)
+        expected = []
+        self._compare_ok(id_, expected, given=response.body)
+
     def test_api_create_existing_user(self):
         id_, params = _build_data(self.apikey, 'create_user',
                                   username=TEST_USER_ADMIN_LOGIN,
--- a/rhodecode/tests/functional/test_compare.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/tests/functional/test_compare.py	Mon Mar 11 17:09:43 2013 +0100
@@ -106,6 +106,7 @@
                                     other_repo=repo2.repo_name,
                                     other_ref_type="branch",
                                     other_ref=rev1,
+                                    merge='1',
                                     ))
 
         response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, rev2, repo2.repo_name, rev1))
@@ -118,9 +119,9 @@
         response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
         response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
         ## files
-        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
+        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=1#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
         #swap
-        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
+        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
 
     def test_compare_forks_on_branch_extra_commits_origin_has_incomming_hg(self):
         self.log_user()
@@ -160,6 +161,7 @@
                                     other_repo=repo2.repo_name,
                                     other_ref_type="branch",
                                     other_ref=rev1,
+                                    merge='x',
                                     ))
         response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, rev2, repo2.repo_name, rev1))
         response.mustcontain("""Showing 2 commits""")
@@ -171,9 +173,9 @@
         response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
         response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
         ## files
-        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
+        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=x#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
         #swap
-        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
+        response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
 
     def test_compare_cherry_pick_changesets_from_bottom(self):
 
@@ -215,20 +217,16 @@
         cs5 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\nline6\n',
                              message='commit6', vcs_type='hg', parent=cs4)
 
-        rev1 = 'tip'
-        rev2 = 'tip'
-
         response = self.app.get(url(controller='compare', action='index',
                                     repo_name=repo2.repo_name,
-                                    org_ref_type="tag",
-                                    org_ref=rev1,
+                                    org_ref_type="rev",
+                                    org_ref=cs1.short_id, # parent of cs2, in repo2
                                     other_repo=repo1.repo_name,
-                                    other_ref_type="tag",
-                                    other_ref=rev2,
-                                    rev_start=cs2.raw_id,
-                                    rev_end=cs4.raw_id,
+                                    other_ref_type="rev",
+                                    other_ref=cs4.short_id,
+                                    merge='True',
                                     ))
-        response.mustcontain('%s@%s -&gt; %s@%s' % (repo2.repo_name, cs2.short_id, repo1.repo_name, cs4.short_id))
+        response.mustcontain('%s@%s -&gt; %s@%s' % (repo2.repo_name, cs1.short_id, repo1.repo_name, cs4.short_id))
         response.mustcontain("""Showing 3 commits""")
         response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
 
@@ -280,21 +278,16 @@
                              message='commit5', vcs_type='hg', parent=cs3)
         cs5 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\nline6\n',
                              message='commit6', vcs_type='hg', parent=cs4)
-        rev1 = 'tip'
-        rev2 = 'tip'
-
         response = self.app.get(url(controller='compare', action='index',
-                                    repo_name=repo2.repo_name,
-                                    org_ref_type="tag",
-                                    org_ref=rev1,
-                                    other_repo=repo1.repo_name,
-                                    other_ref_type="tag",
-                                    other_ref=rev2,
-                                    rev_start=cs3.raw_id,
-                                    rev_end=cs5.raw_id,
+                                    repo_name=repo1.repo_name,
+                                    org_ref_type="rev",
+                                    org_ref=cs2.short_id, # parent of cs3, not in repo2
+                                    other_ref_type="rev",
+                                    other_ref=cs5.short_id,
+                                    merge='1',
                                     ))
 
-        response.mustcontain('%s@%s -&gt; %s@%s' % (repo2.repo_name, cs3.short_id, repo1.repo_name, cs5.short_id))
+        response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, cs2.short_id, repo1.repo_name, cs5.short_id))
         response.mustcontain("""Showing 3 commits""")
         response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
 
@@ -330,6 +323,7 @@
                                     other_ref_type="rev",
                                     other_ref=rev2,
                                     other_repo=HG_FORK,
+                                    merge='1',
                                     ))
         response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
         ## outgoing changesets between those revisions
@@ -339,9 +333,9 @@
         response.mustcontain("""<a href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_FORK, rev2))
 
         ## files
-        response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
-        response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
-        response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
+        response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
+        response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
+        response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
 
     def test_org_repo_new_commits_after_forking_simple_diff(self):
         self.log_user()
@@ -412,6 +406,7 @@
                                     other_ref_type="branch",
                                     other_ref=rev2,
                                     other_repo=r1_name,
+                                    merge='1',
                                     ))
         response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
         response.mustcontain('No files')
@@ -436,6 +431,7 @@
                                     other_ref_type="branch",
                                     other_ref=rev2,
                                     other_repo=r1_name,
+                                    merge='1',
                                     ))
 
         response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
--- a/rhodecode/tests/models/test_repos_groups.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/tests/models/test_repos_groups.py	Mon Mar 11 17:09:43 2013 +0100
@@ -21,6 +21,33 @@
     return gr
 
 
+def _update_group(id_, group_name, desc='desc', parent_id=None):
+    form_data = _get_group_create_params(group_name=group_name,
+                                         group_desc=desc,
+                                         group_parent_id=parent_id)
+    gr = ReposGroupModel().update(id_, form_data)
+    return gr
+
+
+def _make_repo(name, **kwargs):
+    form_data = _get_repo_create_params(repo_name=name, **kwargs)
+    cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+    r = RepoModel().create(form_data, cur_user)
+    return r
+
+
+def _update_repo(name, **kwargs):
+    form_data = _get_repo_create_params(**kwargs)
+    if not 'repo_name' in kwargs:
+        form_data['repo_name'] = name
+    if not 'perms_new' in kwargs:
+        form_data['perms_new'] = []
+    if not 'perms_updates' in kwargs:
+        form_data['perms_updates'] = []
+    r = RepoModel().update(name, **form_data)
+    return r
+
+
 class TestReposGroups(unittest.TestCase):
 
     def setUp(self):
@@ -32,7 +59,7 @@
         Session().commit()
 
     def tearDown(self):
-        print 'out'
+        Session.remove()
 
     def __check_path(self, *path):
         """
@@ -48,21 +75,9 @@
     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,
-            perms_updates=[],
-            perms_new=[],
-            enable_locking=False,
-            recursive=False
-        )
-        gr = ReposGroupModel().update(id_, form_data)
-        return gr
-
     def test_create_group(self):
         g = _make_group('newGroup')
+        Session().commit()
         self.assertEqual(g.full_path, 'newGroup')
 
         self.assertTrue(self.__check_path('newGroup'))
@@ -73,23 +88,27 @@
 
     def test_same_subgroup(self):
         sg1 = _make_group('sub1', parent_id=self.g1.group_id)
+        Session().commit()
         self.assertEqual(sg1.parent_group, self.g1)
         self.assertEqual(sg1.full_path, 'test1/sub1')
         self.assertTrue(self.__check_path('test1', 'sub1'))
 
         ssg1 = _make_group('subsub1', parent_id=sg1.group_id)
+        Session().commit()
         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 = _make_group('deleteme')
+        Session().commit()
         self.__delete_group(sg1.group_id)
 
         self.assertEqual(RepoGroup.get(sg1.group_id), None)
         self.assertFalse(self.__check_path('deteteme'))
 
         sg1 = _make_group('deleteme', parent_id=self.g1.group_id)
+        Session().commit()
         self.__delete_group(sg1.group_id)
 
         self.assertEqual(RepoGroup.get(sg1.group_id), None)
@@ -97,24 +116,26 @@
 
     def test_rename_single_group(self):
         sg1 = _make_group('initial')
+        Session().commit()
 
-        new_sg1 = self.__update_group(sg1.group_id, 'after')
+        new_sg1 = _update_group(sg1.group_id, 'after')
         self.assertTrue(self.__check_path('after'))
         self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
 
     def test_update_group_parent(self):
 
         sg1 = _make_group('initial', parent_id=self.g1.group_id)
+        Session().commit()
 
-        new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
+        new_sg1 = _update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
         self.assertTrue(self.__check_path('test1', 'after'))
         self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
 
-        new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
+        new_sg1 = _update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
         self.assertTrue(self.__check_path('test3', 'after'))
         self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
 
-        new_sg1 = self.__update_group(sg1.group_id, 'hello')
+        new_sg1 = _update_group(sg1.group_id, 'hello')
         self.assertTrue(self.__check_path('hello'))
 
         self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
@@ -123,23 +144,17 @@
 
         g1 = _make_group('g1')
         g2 = _make_group('g2')
-
+        Session().commit()
         # create new repo
-        form_data = _get_repo_create_params(repo_name='john')
-        cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
-        r = RepoModel().create(form_data, cur_user)
-
+        r = _make_repo('john')
+        Session().commit()
         self.assertEqual(r.repo_name, 'john')
-
         # put repo into group
-        form_data = form_data
-        form_data['repo_group'] = g1.group_id
-        form_data['perms_new'] = []
-        form_data['perms_updates'] = []
-        RepoModel().update(r.repo_name, **form_data)
+        r = _update_repo('john', repo_group=g1.group_id)
+        Session().commit()
         self.assertEqual(r.repo_name, 'g1/john')
 
-        self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
+        _update_group(g1.group_id, 'g1', parent_id=g2.group_id)
         self.assertTrue(self.__check_path('g2', 'g1'))
 
         # test repo
@@ -155,7 +170,7 @@
         self.assertEqual(g2.full_path, 't11/t22')
         self.assertTrue(self.__check_path('t11', 't22'))
 
-        g2 = self.__update_group(g2.group_id, 'g22', parent_id=None)
+        g2 = _update_group(g2.group_id, 'g22', parent_id=None)
         Session().commit()
 
         self.assertEqual(g2.group_name, 'g22')
@@ -163,3 +178,65 @@
         self.assertEqual(g2.full_path, 'g22')
         self.assertFalse(self.__check_path('t11', 't22'))
         self.assertTrue(self.__check_path('g22'))
+
+    def test_rename_top_level_group_in_nested_setup(self):
+        g1 = _make_group('L1')
+        Session().commit()
+        g2 = _make_group('L2', parent_id=g1.group_id)
+        Session().commit()
+        g3 = _make_group('L3', parent_id=g2.group_id)
+        Session().commit()
+
+        r = _make_repo('L1/L2/L3/L3_REPO', repo_group=g3.group_id)
+        Session().commit()
+
+        ##rename L1 all groups should be now changed
+        _update_group(g1.group_id, 'L1_NEW')
+        Session().commit()
+        self.assertEqual(g1.full_path, 'L1_NEW')
+        self.assertEqual(g2.full_path, 'L1_NEW/L2')
+        self.assertEqual(g3.full_path, 'L1_NEW/L2/L3')
+        self.assertEqual(r.repo_name,  'L1_NEW/L2/L3/L3_REPO')
+
+    def test_change_parent_of_top_level_group_in_nested_setup(self):
+        g1 = _make_group('R1')
+        Session().commit()
+        g2 = _make_group('R2', parent_id=g1.group_id)
+        Session().commit()
+        g3 = _make_group('R3', parent_id=g2.group_id)
+        Session().commit()
+
+        g4 = _make_group('R1_NEW')
+        Session().commit()
+
+        r = _make_repo('R1/R2/R3/R3_REPO', repo_group=g3.group_id)
+        Session().commit()
+        ##rename L1 all groups should be now changed
+        _update_group(g1.group_id, 'R1', parent_id=g4.group_id)
+        Session().commit()
+        self.assertEqual(g1.full_path, 'R1_NEW/R1')
+        self.assertEqual(g2.full_path, 'R1_NEW/R1/R2')
+        self.assertEqual(g3.full_path, 'R1_NEW/R1/R2/R3')
+        self.assertEqual(r.repo_name,  'R1_NEW/R1/R2/R3/R3_REPO')
+
+    def test_change_parent_of_top_level_group_in_nested_setup_with_rename(self):
+        g1 = _make_group('X1')
+        Session().commit()
+        g2 = _make_group('X2', parent_id=g1.group_id)
+        Session().commit()
+        g3 = _make_group('X3', parent_id=g2.group_id)
+        Session().commit()
+
+        g4 = _make_group('X1_NEW')
+        Session().commit()
+
+        r = _make_repo('X1/X2/X3/X3_REPO', repo_group=g3.group_id)
+        Session().commit()
+
+        ##rename L1 all groups should be now changed
+        _update_group(g1.group_id, 'X1_PRIM', parent_id=g4.group_id)
+        Session().commit()
+        self.assertEqual(g1.full_path, 'X1_NEW/X1_PRIM')
+        self.assertEqual(g2.full_path, 'X1_NEW/X1_PRIM/X2')
+        self.assertEqual(g3.full_path, 'X1_NEW/X1_PRIM/X2/X3')
+        self.assertEqual(r.repo_name,  'X1_NEW/X1_PRIM/X2/X3/X3_REPO')
--- a/rhodecode/tests/scripts/test_vcs_operations.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/tests/scripts/test_vcs_operations.py	Mon Mar 11 17:09:43 2013 +0100
@@ -4,9 +4,10 @@
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
     Test suite for making push/pull operations.
-    Run using::
+    Run using after doing paster serve test.ini::
+     RC_WHOOSH_TEST_DISABLE=1 RC_NO_TMP_PATH=1 nosetests rhodecode/tests/scripts/test_vcs_operations.py
 
-     RC_WHOOSH_TEST_DISABLE=1 RC_NO_TMP_PATH=1 nosetests rhodecode/tests/scripts/test_vcs_operations.py
+    You must have git > 1.8.1 for tests to work fine
 
     :created_on: Dec 30, 2010
     :author: marcink
@@ -107,13 +108,14 @@
     for i in xrange(3):
         cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
         Command(cwd).execute(cmd)
+        author_str = 'Marcin Kuźminski <me@email.com>'
         if vcs == 'hg':
             cmd = """hg commit -m 'commited new %s' -u '%s' %s """ % (
-                i, 'Marcin Kuźminski <marcin@python-blog.com>', added_file
+                i, author_str, added_file
             )
         elif vcs == 'git':
             cmd = """git commit -m 'commited new %s' --author '%s' %s """ % (
-                i, 'Marcin Kuźminski <marcin@python-blog.com>', added_file
+                i, author_str, added_file
             )
         Command(cwd).execute(cmd)
     # PUSH it back
@@ -129,7 +131,7 @@
     if vcs == 'hg':
         stdout, stderr = Command(cwd).execute('hg push --verbose', clone_url)
     elif vcs == 'git':
-        stdout, stderr = Command(cwd).execute('git push', clone_url + " master")
+        stdout, stderr = Command(cwd).execute('git push --verbose', clone_url + " master")
 
     return stdout, stderr
 
@@ -324,8 +326,7 @@
         #pull fails since repo is locked
         clone_url = _construct_url(GIT_REPO)
         stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-        msg = ("""423 Repository `%s` locked by user `%s`"""
-                % (GIT_REPO, TEST_USER_ADMIN_LOGIN))
+        msg = ("""The requested URL returned error: 423""")
         assert msg in stderr
 
     def test_push_on_locked_repo_by_other_user_hg(self):
@@ -455,7 +456,8 @@
             Session().commit()
             clone_url = _construct_url(GIT_REPO)
             stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-            assert 'error: The requested URL returned error: 403 Forbidden' in stderr
+            msg = ("""The requested URL returned error: 403""")
+            assert msg in stderr
         finally:
             #release IP restrictions
             for ip in UserIpMap.getAll():
--- a/rhodecode/tests/vcs/conf.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/rhodecode/tests/vcs/conf.py	Mon Mar 11 17:09:43 2013 +0100
@@ -7,6 +7,7 @@
 import hashlib
 import tempfile
 import datetime
+import shutil
 from rhodecode.tests import *
 from utils import get_normalized_path
 from os.path import join as jn
@@ -58,5 +59,6 @@
 
 PACKAGE_DIR = os.path.abspath(os.path.join(
     os.path.dirname(__file__), '..'))
-
-TEST_USER_CONFIG_FILE = jn(THIS, 'aconfig')
+_dest = jn(TESTS_TMP_PATH,'aconfig')
+shutil.copy(jn(THIS, 'aconfig'), _dest)
+TEST_USER_CONFIG_FILE = _dest
--- a/setup.py	Mon Mar 11 16:43:41 2013 +0100
+++ b/setup.py	Mon Mar 11 17:09:43 2013 +0100
@@ -62,10 +62,10 @@
     requirements.append("argparse")
 
 if is_windows:
-    requirements.append("mercurial==2.5.1")
+    requirements.append("mercurial==2.5.2")
 else:
     requirements.append("py-bcrypt")
-    requirements.append("mercurial==2.5.1")
+    requirements.append("mercurial==2.5.2")
 
 
 dependency_links = [
--- a/test.ini	Mon Mar 11 16:43:41 2013 +0100
+++ b/test.ini	Mon Mar 11 17:09:43 2013 +0100
@@ -31,22 +31,24 @@
 [server:main]
 ## PASTE
 ##nr of threads to spawn
-threadpool_workers = 5
+#threadpool_workers = 5
 
 ##max request before thread respawn
-threadpool_max_requests = 10
+#threadpool_max_requests = 10
 
 ##option to use threads of process
-use_threadpool = true
+#use_threadpool = true
 
-use = egg:Paste#http
+#use = egg:Paste#http
 
 #WAITRESS
 threads = 5
+#100GB
+max_request_body_size = 107374182400
 use = egg:waitress#main
 
 host = 127.0.0.1
-port = 8001
+port = 5000
 
 [filter:proxy-prefix]
 # prefix middleware for rc
@@ -63,6 +65,8 @@
 lang = en
 cache_dir = /tmp/rc/data
 index_dir = /tmp/rc/index
+# set this path to use archive download cache
+#archive_cache_dir = /tmp/rhodecode_tarballcache
 app_instance_uuid = develop-test
 cut_off_limit = 256000
 vcs_full_cache = False
@@ -250,6 +254,87 @@
 #beaker.session.cookie_expires = 3600
 
 
+############################
+## ERROR HANDLING SYSTEMS ##
+############################
+
+####################
+### [errormator] ###
+####################
+
+# Errormator is tailored to work with RhodeCode, see 
+# http://errormator.com for details how to obtain an account
+# you must install python package `errormator_client` to make it work
+
+# errormator enabled
+errormator = true
+
+errormator.server_url = https://api.errormator.com
+errormator.api_key = YOUR_API_KEY
+
+# TWEAK AMOUNT OF INFO SENT HERE
+
+# enables 404 error logging (default False)
+errormator.report_404 = false
+
+# time in seconds after request is considered being slow (default 1)
+errormator.slow_request_time = 1
+
+# record slow requests in application
+# (needs to be enabled for slow datastore recording and time tracking)
+errormator.slow_requests = true
+
+# enable hooking to application loggers
+# errormator.logging = true
+
+# minimum log level for log capture
+# errormator.logging.level = WARNING
+
+# send logs only from erroneous/slow requests
+# (saves API quota for intensive logging)
+errormator.logging_on_error = false
+
+# list of additonal keywords that should be grabbed from environ object 
+# can be string with comma separated list of words in lowercase
+# (by default client will always send following info:
+# 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that 
+# start with HTTP* this list be extended with additional keywords here
+errormator.environ_keys_whitelist = 
+
+
+# list of keywords that should be blanked from request object 
+# can be string with comma separated list of words in lowercase
+# (by default client will always blank keys that contain following words 
+# 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
+# this list be extended with additional keywords set here
+errormator.request_keys_blacklist =
+
+
+# list of namespaces that should be ignores when gathering log entries
+# can be string with comma separated list of namespaces
+# (by default the client ignores own entries: errormator_client.client)
+errormator.log_namespace_blacklist =  
+
+
+################
+### [sentry] ###
+################
+
+# sentry is a alternative open source error aggregator
+# you must install python packages `sentry` and `raven` to enable 
+
+sentry.dsn = YOUR_DNS
+sentry.servers =
+sentry.name =
+sentry.key =
+sentry.public_key =
+sentry.secret_key =
+sentry.project =
+sentry.site =
+sentry.include_paths =
+sentry.exclude_paths =
+
+
 ################################################################################
 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*  ##
 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to  ##
@@ -270,7 +355,6 @@
 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.sqlite
 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_test
 #sqlalchemy.db1.url = mysql://root:qwe@localhost/rhodecode_test
-
 sqlalchemy.db1.echo = false
 sqlalchemy.db1.pool_recycle = 3600
 sqlalchemy.db1.convert_unicode = true