changeset 5268:743c288a2db0

Merge stable
author Mads Kiilerich <madski@unity3d.com>
date Mon, 20 Jul 2015 15:07:54 +0200
parents f103b1a2383b (diff) 763dc7a96bae (current diff)
children 14e2291a8f0b
files kallithea/templates/admin/gists/edit.html
diffstat 203 files changed, 4949 insertions(+), 4209 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Mon Jul 20 15:07:23 2015 +0200
+++ b/.hgignore	Mon Jul 20 15:07:54 2015 +0200
@@ -6,6 +6,8 @@
 *.egg-info
 *.egg
 *.mo
+.eggs/
+tarballcache/
 
 syntax: regexp
 ^rcextensions
--- a/development.ini	Mon Jul 20 15:07:23 2015 +0200
+++ b/development.ini	Mon Jul 20 15:07:54 2015 +0200
@@ -134,7 +134,7 @@
 host = 0.0.0.0
 port = 5000
 
-## prefix middleware for rc
+## middleware for hosting the WSGI application under a URL prefix
 #[filter:proxy-prefix]
 #use = egg:PasteDeploy#prefix
 #prefix = /<your-prefix>
--- a/docs/index.rst	Mon Jul 20 15:07:23 2015 +0200
+++ b/docs/index.rst	Mon Jul 20 15:07:54 2015 +0200
@@ -15,6 +15,7 @@
 .. toctree::
    :maxdepth: 1
 
+   overview
    installation
    installation_win
    installation_win_old
--- a/docs/installation.rst	Mon Jul 20 15:07:23 2015 +0200
+++ b/docs/installation.rst	Mon Jul 20 15:07:54 2015 +0200
@@ -4,16 +4,11 @@
 Installation on Unix/Linux
 ==========================
 
-**Kallithea** is written entirely in Python_ and requires Python version
-2.6 or higher. Python 3.x is currently not supported.
-
-There are several ways to install Kallithea:
+Here are more details about 3 ways to install Kallithea:
 
-- :ref:`installation-source`: The Kallithea development repository is stable
-  and can be used in production. In fact, the Kallithea maintainers do
-  use it in production. The advantage of installation from source and regularly
-  updating it is that you take advantage of the most recent improvements, which
-  is particularly useful because Kallithea is evolving rapidly.
+- :ref:`installation-source`: The simplest way to keep the installation
+  uptodate and keep track of local customizations is to run directly from
+  source in a Kallithea repository clone and use virtualenv.
 
 - :ref:`installation-virtualenv`: If you prefer to only use released versions
   of Kallithea, the recommended method is to install Kallithea in a virtual
@@ -205,5 +200,4 @@
 
 
 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
-.. _Python: http://www.python.org/
 .. _pylons: http://www.pylonsproject.org/
--- a/docs/installation_win_old.rst	Mon Jul 20 15:07:23 2015 +0200
+++ b/docs/installation_win_old.rst	Mon Jul 20 15:07:54 2015 +0200
@@ -211,7 +211,7 @@
   cd C:\Kallithea\Bin
   paster make-config Kallithea production.ini
 
-Then, you must edit production.ini to fit your needs (ip address, ip
+Then, you must edit production.ini to fit your needs (network address and
 port, mail settings, database, whatever). I recommend using NotePad++
 (free) or similar text editor, as it handles well the EndOfLine
 character differences between Unix and Windows
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/overview.rst	Mon Jul 20 15:07:54 2015 +0200
@@ -0,0 +1,128 @@
+.. _overview:
+
+=====================
+Installation Overview
+=====================
+
+
+Some overview and some details that can help understanding the options when
+installing Kallithea.
+
+
+Python Environment
+------------------
+
+**Kallithea** is written entirely in Python_ and requires Python version
+2.6 or higher. Python 3.x is currently not supported.
+
+Given a Python installation, there are different ways of providing the
+environment for running Python applications. Each of them pretty much
+corresponds to a ``site-packages`` directory somewhere where packages can be
+installed.
+
+Kallithea itself can be run from source or be installed, but even when running
+from source, there are some dependencies that must be installed in the Python
+environment used for running Kallithea.
+
+- Packages *could* be installed in Python's ``site-packages`` directory ... but
+  that would require running pip_ as root and it would be hard to uninstall or
+  upgrade and is probably not a good idea unless using a package manager.
+
+- Packages could also be installed in ``~/.local`` ... but that is probably
+  only a good idea if using a dedicated user per application or instance.
+
+- Finally, it can be installed in a virtualenv_. That is a very lightweight
+  "container" where each Kallithea instance can get its own dedicated and
+  self-contained virtual environment.
+
+We recommend using virtualenv for installing Kallithea.
+
+
+Installation Methods
+--------------------
+
+Kallithea must be installed on a server. Kallithea is installed in a Python
+environment so it can use packages that are installed there and make itself
+available for other packages.
+
+Two different cases will pretty much cover the options for how it can be
+installed.
+
+- The Kallithea source repository can be cloned and used - it is kept stable and
+  can be used in production. The Kallithea maintainers use the development
+  branch in production. The advantage of installation from source and regularly
+  updating it is that you take advantage of the most recent improvements. Using
+  it directly from a DVCS also means that it is easy to track local customizations.
+
+  Running ``setup.py develop`` in the source will use pip to install the
+  necessary dependencies in the Python environment and create a
+  ``.../site-packages/Kallithea.egg-link`` file there that points at the Kallithea
+  source.
+
+- Kallithea can also be installed from ready-made packages using a package manager.
+  The official released versions are available on PyPI_ and can be downloaded and
+  installed with all dependencies using ``pip install kallithea``.
+
+  With this method, Kallithea is installed in the Python environment as any
+  other package, usually as a ``.../site-packages/Kallithea-X-py2.7.egg/``
+  directory with Python files and everything else that is needed.
+
+  (``pip install kallithea`` from a source tree will do pretty much the same
+  but build the Kallithea package itself locally instead of downloading it.)
+
+
+Web Server
+----------
+
+Kallithea is (primarily) a WSGI_ application that must be run from a web
+server that expose WSGI as HTTP.
+
+- Kallithea uses the Paste_ tool for some admin tasks. Paste provides ``paste
+  serve`` as a convenient way to launch Python WSGI / web servers.
+  This method is perfect for development but *can* also be used for production.
+
+  ``paste`` is a command line tool. Using it in production requires some way to
+  wrap it as a managable service.
+
+  Paste come with its own web server but Kallithea defaults to use Waitress_.
+  Gunicorn_ is also an option. These web servers have different limited feature
+  sets.
+
+  It is also common/mandatory to put another web server or (reverse) proxy in
+  front of these Python web servers. Nginx_ is a common choice. This simple
+  setup will thus often end up being quite complex.
+
+  The configuration of which web server to use is in the ini file passed to
+  ``paste``. The entry point for the WSGI application is configured in
+  ``setup.py`` as ``kallithea.config.middleware:make_app``.
+
+- `Apache httpd`_ can serve WSGI applications directly using mod_wsgi_ and a
+  simple Python file with the necessary configuration. This is a good option if
+  Apache is an option.
+
+- IIS_ can also server WSGI applications directly using isapi-wsgi_.
+
+- UWSGI_ is also an option.
+
+The best option depends on what you are familiar with and the requirements for
+performance and stability. Also, keep in mind that Kallithea mainly is serving
+custom data generated from relatively slow Python process. Kallithea is also
+often used inside organizations with a limited amount of users and thus no
+continuous hammering from the internet.
+
+
+.. _Python: http://www.python.org/
+.. _Gunicorn: http://gunicorn.org/
+.. _Waitress: http://waitress.readthedocs.org/en/latest/
+.. _virtualenv: http://pypi.python.org/pypi/virtualenv
+.. _Paste: http://pythonpaste.org/
+.. _PyPI: https://pypi.python.org/pypi
+.. _Apache httpd: http://httpd.apache.org/
+.. _mod_wsgi: https://code.google.com/p/modwsgi/
+.. _isapi-wsgi: https://github.com/hexdump42/isapi-wsgi
+.. _UWSGI: https://uwsgi-docs.readthedocs.org/en/latest/
+.. _nginx: http://nginx.org/en/
+.. _iis: http://en.wikipedia.org/wiki/Internet_Information_Services
+.. _pip: http://en.wikipedia.org/wiki/Pip_%28package_manager%29
+.. _WSGI: http://en.wikipedia.org/wiki/Web_Server_Gateway_Interface
+.. _pylons: http://www.pylonsproject.org/
--- a/docs/setup.rst	Mon Jul 20 15:07:23 2015 +0200
+++ b/docs/setup.rst	Mon Jul 20 15:07:54 2015 +0200
@@ -68,16 +68,21 @@
   settings, as well as edit more advanced options on users and
   repositories
 
+
+Extensions
+----------
+
 Optionally users can create an ``rcextensions`` package that extends Kallithea
 functionality. To do this simply execute::
 
     paster make-rcext my.ini
 
-This will create an ``rcextensions`` package in the same place that your ``ini`` file
-lives. With ``rcextensions`` it's possible to add additional mapping for whoosh,
+This will create an ``rcextensions`` package next to the specified ``ini`` file.
+With ``rcextensions`` it's possible to add additional mapping for whoosh,
 stats and add additional code into the push/pull/create/delete repo hooks,
-for example, for sending signals to build-bots such as Jenkins.
-Please see the ``__init__.py`` file inside ``rcextensions`` package
+for example for sending signals to build-bots such as Jenkins.
+
+See the ``__init__.py`` file inside the generated ``rcextensions`` package
 for more details.
 
 
--- a/docs/usage/performance.rst	Mon Jul 20 15:07:23 2015 +0200
+++ b/docs/usage/performance.rst	Mon Jul 20 15:07:54 2015 +0200
@@ -39,7 +39,8 @@
     sqlite is a good option when having a small load on the system. But due to
     locking issues with sqlite, it is not recommended to use it for larger
     deployments. Switching to mysql or postgres will result in an immediate
-    performance increase.
+    performance increase. A tool like SQLAlchemyGrate_ can be used for
+    migrating to another database platform.
 
 3. Scale Kallithea horizontally
 
@@ -61,3 +62,5 @@
     - Load balance using round robin or IP hash, recommended is writing LB rules
       that will separate regular user traffic from automated processes like CI
       servers or build bots.
+
+.. _SQLAlchemyGrate: https://github.com/shazow/sqlalchemygrate
--- a/kallithea/bin/template.ini.mako	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/bin/template.ini.mako	Mon Jul 20 15:07:54 2015 +0200
@@ -132,7 +132,7 @@
 host = ${host}
 port = ${port}
 
-<%text>## prefix middleware for rc</%text>
+<%text>## middleware for hosting the WSGI application under a URL prefix</%text>
 #[filter:proxy-prefix]
 #use = egg:PasteDeploy#prefix
 #prefix = /<your-prefix>
--- a/kallithea/config/deployment.ini_tmpl	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/config/deployment.ini_tmpl	Mon Jul 20 15:07:54 2015 +0200
@@ -129,7 +129,7 @@
 host = 127.0.0.1
 port = 5000
 
-## prefix middleware for rc
+## middleware for hosting the WSGI application under a URL prefix
 #[filter:proxy-prefix]
 #use = egg:PasteDeploy#prefix
 #prefix = /<your-prefix>
--- a/kallithea/config/middleware.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/config/middleware.py	Mon Jul 20 15:07:54 2015 +0200
@@ -15,7 +15,6 @@
     Pylons middleware initialization
 """
 
-from beaker.middleware import SessionMiddleware
 from routes.middleware import RoutesMiddleware
 from paste.cascade import Cascade
 from paste.registry import RegistryManager
@@ -29,6 +28,7 @@
 from kallithea.lib.middleware.simplehg import SimpleHg
 from kallithea.lib.middleware.simplegit import SimpleGit
 from kallithea.lib.middleware.https_fixup import HttpsFixup
+from kallithea.lib.middleware.sessionmiddleware import SecureSessionMiddleware
 from kallithea.config.environment import load_environment
 from kallithea.lib.middleware.wrapper import RequestWrapper
 
@@ -60,7 +60,7 @@
 
     # Routing/Session/Cache Middleware
     app = RoutesMiddleware(app, config['routes.map'])
-    app = SessionMiddleware(app, config)
+    app = SecureSessionMiddleware(app, config)
 
     # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
     if asbool(config['pdebug']):
--- a/kallithea/config/routing.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/config/routing.py	Mon Jul 20 15:07:54 2015 +0200
@@ -206,7 +206,7 @@
         m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys",
                   action="edit_api_keys", conditions=dict(method=["GET"]))
         m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys",
-                  action="add_api_key", conditions=dict(method=["PUT"]))
+                  action="add_api_key", conditions=dict(method=["POST"]))
         m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys",
                   action="delete_api_key", conditions=dict(method=["DELETE"]))
 
@@ -699,11 +699,6 @@
                  action='create', conditions=dict(function=check_repo,
                                                   method=["POST"]))
 
-    rmap.connect('pullrequest_copy_update',
-                 '/{repo_name:.*?}/pull-request-update/{pull_request_id}', controller='pullrequests',
-                 action='copy_update', conditions=dict(function=check_repo,
-                                                       method=["POST"]))
-
     rmap.connect('pullrequest_show',
                  '/{repo_name:.*?}/pull-request/{pull_request_id:\\d+}{extra:(/.*)?}', extra='',
                  controller='pullrequests',
--- a/kallithea/controllers/admin/auth_settings.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/admin/auth_settings.py	Mon Jul 20 15:07:54 2015 +0200
@@ -23,7 +23,6 @@
 :author: akesterson
 """
 
-import pprint
 import logging
 import formencode.htmlfill
 import traceback
@@ -61,56 +60,64 @@
         ]
         c.enabled_plugins = Setting.get_auth_plugins()
 
-    def index(self, defaults=None, errors=None, prefix_error=False):
-        self.__load_defaults()
-        _defaults = {}
-        # default plugins loaded
-        formglobals = {
-            "auth_plugins": ["kallithea.lib.auth_modules.auth_internal"]
-        }
-        formglobals.update(Setting.get_auth_settings())
-        formglobals["plugin_settings"] = {}
-        formglobals["auth_plugins_shortnames"] = {}
-        _defaults["auth_plugins"] = formglobals["auth_plugins"]
+    def __render(self, defaults, errors):
+        c.defaults = {}
+        c.plugin_settings = {}
+        c.plugin_shortnames = {}
 
-        for module in formglobals["auth_plugins"]:
+        for module in c.enabled_plugins:
             plugin = auth_modules.loadplugin(module)
             plugin_name = plugin.name
-            formglobals["auth_plugins_shortnames"][module] = plugin_name
-            formglobals["plugin_settings"][module] = plugin.plugin_settings()
-            for v in formglobals["plugin_settings"][module]:
+            c.plugin_shortnames[module] = plugin_name
+            c.plugin_settings[module] = plugin.plugin_settings()
+            for v in c.plugin_settings[module]:
                 fullname = ("auth_" + plugin_name + "_" + v["name"])
                 if "default" in v:
-                    _defaults[fullname] = v["default"]
+                    c.defaults[fullname] = v["default"]
                 # Current values will be the default on the form, if there are any
                 setting = Setting.get_by_name(fullname)
                 if setting:
-                    _defaults[fullname] = setting.app_settings_value
+                    c.defaults[fullname] = setting.app_settings_value
         # we want to show , separated list of enabled plugins
-        _defaults['auth_plugins'] = ','.join(_defaults['auth_plugins'])
-        if defaults:
-            _defaults.update(defaults)
+        c.defaults['auth_plugins'] = ','.join(c.enabled_plugins)
 
-        formglobals["defaults"] = _defaults
-        # set template context variables
-        for k, v in formglobals.iteritems():
-            setattr(c, k, v)
+        if defaults:
+            c.defaults.update(defaults)
 
-        log.debug(pprint.pformat(formglobals, indent=4))
         log.debug(formatted_json(defaults))
         return formencode.htmlfill.render(
             render('admin/auth/auth_settings.html'),
-            defaults=_defaults,
+            defaults=c.defaults,
             errors=errors,
-            prefix_error=prefix_error,
+            prefix_error=False,
             encoding="UTF-8",
             force_defaults=False)
 
+    def index(self):
+        self.__load_defaults()
+        return self.__render(defaults=None, errors=None)
+
     def auth_settings(self):
         """POST create and store auth settings"""
         self.__load_defaults()
+        log.debug("POST Result: %s", formatted_json(dict(request.POST)))
+
+        # First, parse only the plugin list (not the plugin settings).
+        _auth_plugins_validator = AuthSettingsForm([]).fields['auth_plugins']
+        try:
+            new_enabled_plugins = _auth_plugins_validator.to_python(request.POST.get('auth_plugins'))
+        except formencode.Invalid:
+            pass
+        else:
+            # Hide plugins that the user has asked to be disabled, but
+            # do not show plugins that the user has asked to be enabled
+            # (yet), since that'll cause validation errors and/or wrong
+            # settings being applied (e.g. checkboxes being cleared),
+            # since the plugin settings will not be in the POST data.
+            c.enabled_plugins = [ p for p in c.enabled_plugins if p in new_enabled_plugins ]
+
+        # Next, parse everything including plugin settings.
         _form = AuthSettingsForm(c.enabled_plugins)()
-        log.debug("POST Result: %s" % formatted_json(dict(request.POST)))
 
         try:
             form_result = _form.to_python(dict(request.POST))
@@ -127,10 +134,10 @@
         except formencode.Invalid, errors:
             log.error(traceback.format_exc())
             e = errors.error_dict or {}
-            return self.index(
+            return self.__render(
                 defaults=errors.value,
                 errors=e,
-                prefix_error=False)
+            )
         except Exception:
             log.error(traceback.format_exc())
             h.flash(_('error occurred during update of auth settings'),
--- a/kallithea/controllers/admin/gists.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/admin/gists.py	Mon Jul 20 15:07:54 2015 +0200
@@ -56,7 +56,7 @@
 
     def __load_defaults(self, extra_values=None):
         c.lifetime_values = [
-            (str(-1), _('forever')),
+            (str(-1), _('Forever')),
             (str(5), _('5 minutes')),
             (str(60), _('1 hour')),
             (str(60 * 24), _('1 day')),
@@ -230,7 +230,7 @@
             log.error(traceback.format_exc())
             raise HTTPNotFound()
 
-        self.__load_defaults(extra_values=('0', _('unmodified')))
+        self.__load_defaults(extra_values=('0', _('Unmodified')))
         rendered = render('admin/gists/edit.html')
 
         if request.POST:
--- a/kallithea/controllers/admin/my_account.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/admin/my_account.py	Mon Jul 20 15:07:54 2015 +0200
@@ -98,8 +98,8 @@
         # url('my_account')
         c.active = 'profile'
         self.__load_data()
-        c.perm_user = AuthUser(user_id=self.authuser.user_id,
-                               ip_addr=self.ip_addr)
+        c.perm_user = AuthUser(user_id=self.authuser.user_id)
+        c.ip_addr = self.ip_addr
         c.extern_type = c.user.extern_type
         c.extern_name = c.user.extern_name
 
@@ -193,8 +193,8 @@
     def my_account_perms(self):
         c.active = 'perms'
         self.__load_data()
-        c.perm_user = AuthUser(user_id=self.authuser.user_id,
-                               ip_addr=self.ip_addr)
+        c.perm_user = AuthUser(user_id=self.authuser.user_id)
+        c.ip_addr = self.ip_addr
 
         return render('admin/my_account/my_account.html')
 
@@ -235,7 +235,7 @@
         self.__load_data()
         show_expired = True
         c.lifetime_values = [
-            (str(-1), _('forever')),
+            (str(-1), _('Forever')),
             (str(5), _('5 minutes')),
             (str(60), _('1 hour')),
             (str(60 * 24), _('1 day')),
@@ -251,7 +251,7 @@
         description = request.POST.get('description')
         ApiKeyModel().create(self.authuser.user_id, description, lifetime)
         Session().commit()
-        h.flash(_("Api key successfully created"), category='success')
+        h.flash(_("API key successfully created"), category='success')
         return redirect(url('my_account_api_keys'))
 
     def my_account_api_keys_delete(self):
@@ -263,10 +263,10 @@
                 user.api_key = generate_api_key()
                 Session().add(user)
                 Session().commit()
-                h.flash(_("Api key successfully reset"), category='success')
+                h.flash(_("API key successfully reset"), category='success')
         elif api_key:
             ApiKeyModel().delete(api_key, self.authuser.user_id)
             Session().commit()
-            h.flash(_("Api key successfully deleted"), category='success')
+            h.flash(_("API key successfully deleted"), category='success')
 
         return redirect(url('my_account_api_keys'))
--- a/kallithea/controllers/admin/repos.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/admin/repos.py	Mon Jul 20 15:07:54 2015 +0200
@@ -370,6 +370,8 @@
 
         c.repo_fields = RepositoryField.query()\
             .filter(RepositoryField.repository == c.repo_info).all()
+        repo_model = RepoModel()
+        c.users_array = repo_model.get_users_js()
         c.active = 'settings'
         return htmlfill.render(
             render('admin/repos/repo_edit.html'),
--- a/kallithea/controllers/admin/users.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/admin/users.py	Mon Jul 20 15:07:54 2015 +0200
@@ -34,6 +34,7 @@
 from pylons.controllers.util import redirect
 from pylons.i18n.translation import _
 from sqlalchemy.sql.expression import func
+from webob.exc import HTTPNotFound
 
 import kallithea
 from kallithea.lib.exceptions import DefaultUserException, \
@@ -167,7 +168,8 @@
         c.user = user_model.get(id)
         c.extern_type = c.user.extern_type
         c.extern_name = c.user.extern_name
-        c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
+        c.perm_user = AuthUser(user_id=id)
+        c.ip_addr = self.ip_addr
         _form = UserForm(edit=True, old_data={'user_id': id,
                                               'email': c.user.email})()
         form_result = {}
@@ -233,18 +235,22 @@
         # url('user', id=ID)
         User.get_or_404(-1)
 
+    def _get_user_or_raise_if_default(self, id):
+        try:
+            return User.get_or_404(id, allow_default=False)
+        except DefaultUserException:
+            h.flash(_("The default user cannot be edited"), category='warning')
+            raise HTTPNotFound
+
     def edit(self, id, format='html'):
         """GET /users/id/edit: Form to edit an existing item"""
         # url('edit_user', id=ID)
-        c.user = User.get_or_404(id)
-        if c.user.username == User.DEFAULT_USER:
-            h.flash(_("You can't edit this user"), category='warning')
-            return redirect(url('users'))
-
+        c.user = self._get_user_or_raise_if_default(id)
         c.active = 'profile'
         c.extern_type = c.user.extern_type
         c.extern_name = c.user.extern_name
-        c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
+        c.perm_user = AuthUser(user_id=id)
+        c.ip_addr = self.ip_addr
 
         defaults = c.user.get_dict()
         return htmlfill.render(
@@ -254,13 +260,10 @@
             force_defaults=False)
 
     def edit_advanced(self, id):
-        c.user = User.get_or_404(id)
-        if c.user.username == User.DEFAULT_USER:
-            h.flash(_("You can't edit this user"), category='warning')
-            return redirect(url('users'))
-
+        c.user = self._get_user_or_raise_if_default(id)
         c.active = 'advanced'
-        c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
+        c.perm_user = AuthUser(user_id=id)
+        c.ip_addr = self.ip_addr
 
         umodel = UserModel()
         defaults = c.user.get_dict()
@@ -277,15 +280,11 @@
             force_defaults=False)
 
     def edit_api_keys(self, id):
-        c.user = User.get_or_404(id)
-        if c.user.username == User.DEFAULT_USER:
-            h.flash(_("You can't edit this user"), category='warning')
-            return redirect(url('users'))
-
+        c.user = self._get_user_or_raise_if_default(id)
         c.active = 'api_keys'
         show_expired = True
         c.lifetime_values = [
-            (str(-1), _('forever')),
+            (str(-1), _('Forever')),
             (str(5), _('5 minutes')),
             (str(60), _('1 hour')),
             (str(60 * 24), _('1 day')),
@@ -302,23 +301,17 @@
             force_defaults=False)
 
     def add_api_key(self, id):
-        c.user = User.get_or_404(id)
-        if c.user.username == User.DEFAULT_USER:
-            h.flash(_("You can't edit this user"), category='warning')
-            return redirect(url('users'))
+        c.user = self._get_user_or_raise_if_default(id)
 
         lifetime = safe_int(request.POST.get('lifetime'), -1)
         description = request.POST.get('description')
         ApiKeyModel().create(c.user.user_id, description, lifetime)
         Session().commit()
-        h.flash(_("Api key successfully created"), category='success')
+        h.flash(_("API key successfully created"), category='success')
         return redirect(url('edit_user_api_keys', id=c.user.user_id))
 
     def delete_api_key(self, id):
-        c.user = User.get_or_404(id)
-        if c.user.username == User.DEFAULT_USER:
-            h.flash(_("You can't edit this user"), category='warning')
-            return redirect(url('users'))
+        c.user = self._get_user_or_raise_if_default(id)
 
         api_key = request.POST.get('del_api_key')
         if request.POST.get('del_api_key_builtin'):
@@ -327,11 +320,11 @@
                 user.api_key = generate_api_key()
                 Session().add(user)
                 Session().commit()
-                h.flash(_("Api key successfully reset"), category='success')
+                h.flash(_("API key successfully reset"), category='success')
         elif api_key:
             ApiKeyModel().delete(api_key, c.user.user_id)
             Session().commit()
-            h.flash(_("Api key successfully deleted"), category='success')
+            h.flash(_("API key successfully deleted"), category='success')
 
         return redirect(url('edit_user_api_keys', id=c.user.user_id))
 
@@ -339,13 +332,10 @@
         pass
 
     def edit_perms(self, id):
-        c.user = User.get_or_404(id)
-        if c.user.username == User.DEFAULT_USER:
-            h.flash(_("You can't edit this user"), category='warning')
-            return redirect(url('users'))
-
+        c.user = self._get_user_or_raise_if_default(id)
         c.active = 'perms'
-        c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
+        c.perm_user = AuthUser(user_id=id)
+        c.ip_addr = self.ip_addr
 
         umodel = UserModel()
         defaults = c.user.get_dict()
@@ -364,7 +354,7 @@
     def update_perms(self, id):
         """PUT /users_perm/id: Update an existing item"""
         # url('user_perm', id=ID, method='put')
-        user = User.get_or_404(id)
+        user = self._get_user_or_raise_if_default(id)
 
         try:
             form = CustomDefaultPermissionsForm()()
@@ -402,11 +392,7 @@
         return redirect(url('edit_user_perms', id=id))
 
     def edit_emails(self, id):
-        c.user = User.get_or_404(id)
-        if c.user.username == User.DEFAULT_USER:
-            h.flash(_("You can't edit this user"), category='warning')
-            return redirect(url('users'))
-
+        c.user = self._get_user_or_raise_if_default(id)
         c.active = 'emails'
         c.user_email_map = UserEmailMap.query()\
             .filter(UserEmailMap.user == c.user).all()
@@ -421,7 +407,7 @@
     def add_email(self, id):
         """POST /user_emails:Add an existing item"""
         # url('user_emails', id=ID, method='put')
-
+        user = self._get_user_or_raise_if_default(id)
         email = request.POST.get('new_email')
         user_model = UserModel()
 
@@ -441,6 +427,7 @@
     def delete_email(self, id):
         """DELETE /user_emails_delete/id: Delete an existing item"""
         # url('user_emails_delete', id=ID, method='delete')
+        user = self._get_user_or_raise_if_default(id)
         email_id = request.POST.get('del_email_id')
         user_model = UserModel()
         user_model.delete_extra_email(id, email_id)
@@ -449,11 +436,7 @@
         return redirect(url('edit_user_emails', id=id))
 
     def edit_ips(self, id):
-        c.user = User.get_or_404(id)
-        if c.user.username == User.DEFAULT_USER:
-            h.flash(_("You can't edit this user"), category='warning')
-            return redirect(url('users'))
-
+        c.user = self._get_user_or_raise_if_default(id)
         c.active = 'ips'
         c.user_ip_map = UserIpMap.query()\
             .filter(UserIpMap.user == c.user).all()
@@ -479,7 +462,7 @@
         try:
             user_model.add_extra_ip(id, ip)
             Session().commit()
-            h.flash(_("Added ip %s to user whitelist") % ip, category='success')
+            h.flash(_("Added IP address %s to user whitelist") % ip, category='success')
         except formencode.Invalid, error:
             msg = error.error_dict['ip']
             h.flash(msg, category='error')
@@ -499,7 +482,7 @@
         user_model = UserModel()
         user_model.delete_extra_ip(id, ip_id)
         Session().commit()
-        h.flash(_("Removed ip address from user whitelist"), category='success')
+        h.flash(_("Removed IP address from user whitelist"), category='success')
 
         if 'default_user' in request.POST:
             return redirect(url('admin_permissions_ips'))
--- a/kallithea/controllers/api/__init__.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/api/__init__.py	Mon Jul 20 15:07:54 2015 +0200
@@ -134,7 +134,7 @@
                                  message="JSON parse error ERR:%s RAW:%r"
                                  % (e, raw_body))
 
-        # check AUTH based on API KEY
+        # check AUTH based on API key
         try:
             self._req_api_key = json_body['api_key']
             self._req_id = json_body['id']
@@ -156,11 +156,10 @@
             u = User.get_by_api_key(self._req_api_key)
             if u is None:
                 return jsonrpc_error(retid=self._req_id,
-                                     message='Invalid API KEY')
+                                     message='Invalid API key')
 
-            #check if we are allowed to use this IP
-            auth_u = AuthUser(u.user_id, self._req_api_key, ip_addr=ip_addr)
-            if not auth_u.ip_allowed:
+            auth_u = AuthUser(u.user_id, self._req_api_key)
+            if not AuthUser.check_ip_allowed(auth_u, ip_addr):
                 return jsonrpc_error(retid=self._req_id,
                         message='request from IP:%s not allowed' % (ip_addr,))
             else:
@@ -168,7 +167,7 @@
 
         except Exception, e:
             return jsonrpc_error(retid=self._req_id,
-                                 message='Invalid API KEY')
+                                 message='Invalid API key')
 
         self._error = None
         try:
@@ -208,7 +207,7 @@
         # get our arglist and check if we provided them as args
         for arg, default in func_kwargs.iteritems():
             if arg == USER_SESSION_ATTR:
-                # USER_SESSION_ATTR is something translated from api key and
+                # USER_SESSION_ATTR is something translated from API key and
                 # this is checked before so we don't need validate it
                 continue
 
--- a/kallithea/controllers/api/api.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/api/api.py	Mon Jul 20 15:07:54 2015 +0200
@@ -553,7 +553,7 @@
                     {
                         "user_id" :     "<user_id>",
                         "api_key" :     "<api_key>",
-                        "api_keys":     "[<list of all api keys including additional ones>]"
+                        "api_keys":     "[<list of all API keys including additional ones>]"
                         "username" :    "<username>",
                         "firstname":    "<firstname>",
                         "lastname" :    "<lastname>",
--- a/kallithea/controllers/changeset.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/changeset.py	Mon Jul 20 15:07:54 2015 +0200
@@ -166,7 +166,7 @@
     if ig_ws:
         params[ig_ws_key] += [ig_ws_val]
 
-    lbl = _('increase diff context to %(num)s lines') % {'num': ln_ctx}
+    lbl = _('Increase diff context to %(num)s lines') % {'num': ln_ctx}
 
     params['anchor'] = fileid
     icon = h.literal('<i class="icon-sort"></i>')
@@ -208,7 +208,7 @@
                 raise RepositoryError('Changeset range returned empty result')
 
         except(ChangesetDoesNotExistError,), e:
-            log.error(traceback.format_exc())
+            log.debug(traceback.format_exc())
             msg = _('Such revision does not exist for this repository')
             h.flash(msg, category='error')
             raise HTTPNotFound()
@@ -349,9 +349,9 @@
     @jsonify
     def comment(self, repo_name, revision):
         status = request.POST.get('changeset_status')
-        text = request.POST.get('text', '').strip() or _('No comments.')
+        text = request.POST.get('text', '').strip()
 
-        c.co = comm = ChangesetCommentsModel().create(
+        c.comment = comment = ChangesetCommentsModel().create(
             text=text,
             repo=c.db_repo.repo_id,
             user=c.authuser.user_id,
@@ -373,12 +373,12 @@
                     c.db_repo.repo_id,
                     status,
                     c.authuser.user_id,
-                    comm,
+                    comment,
                     revision=revision,
                     dont_allow_on_closed_pull_request=True
                 )
             except StatusChangeOnClosedPullRequestError:
-                log.error(traceback.format_exc())
+                log.debug(traceback.format_exc())
                 msg = _('Changing status on a changeset associated with '
                         'a closed pull request is not allowed')
                 h.flash(msg, category='warning')
@@ -397,8 +397,8 @@
         data = {
            'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
         }
-        if comm:
-            data.update(comm.get_dict())
+        if comment:
+            data.update(comment.get_dict())
             data.update({'rendered_text':
                          render('changeset/changeset_comment_block.html')})
 
--- a/kallithea/controllers/compare.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/compare.py	Mon Jul 20 15:07:54 2015 +0200
@@ -70,7 +70,7 @@
         :param other_rev: revision we want out compare to be made on other_repo
         """
         ancestor = None
-        if org_rev == other_rev or org_repo.EMPTY_CHANGESET in (org_rev, other_rev):
+        if org_rev == other_rev:
             org_changesets = []
             other_changesets = []
             ancestor = org_rev
@@ -88,14 +88,18 @@
             else:
                 hgrepo = other_repo._repo
 
-            ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev)
-            if ancestors:
-                # FIXME: picks arbitrary ancestor - but there is usually only one
-                try:
-                    ancestor = hgrepo[ancestors.first()].hex()
-                except AttributeError:
-                    # removed in hg 3.2
-                    ancestor = hgrepo[ancestors[0]].hex()
+            if org_repo.EMPTY_CHANGESET in (org_rev, other_rev):
+                # work around unexpected behaviour in Mercurial < 3.4
+                ancestor = org_repo.EMPTY_CHANGESET
+            else:
+                ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev)
+                if ancestors:
+                    # FIXME: picks arbitrary ancestor - but there is usually only one
+                    try:
+                        ancestor = hgrepo[ancestors.first()].hex()
+                    except AttributeError:
+                        # removed in hg 3.2
+                        ancestor = hgrepo[ancestors[0]].hex()
 
             other_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
                                      other_rev, org_rev, org_rev)
@@ -130,13 +134,13 @@
 
             else:
                 so, se = org_repo.run_git_command(
-                    'log --reverse --pretty="format: %%H" -s %s..%s'
-                        % (org_rev, other_rev)
+                    ['log', '--reverse', '--pretty=format:%H',
+                     '-s', '%s..%s' % (org_rev, other_rev)]
                 )
                 other_changesets = [org_repo.get_changeset(cs)
                               for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
                 so, se = org_repo.run_git_command(
-                    'merge-base %s %s' % (org_rev, other_rev)
+                    ['merge-base', org_rev, other_rev]
                 )
                 ancestor = re.findall(r'[0-9a-fA-F]{40}', so)[0]
             org_changesets = []
--- a/kallithea/controllers/error.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/error.py	Mon Jul 20 15:07:54 2015 +0200
@@ -32,7 +32,7 @@
 
 from pylons import tmpl_context as c, request, config
 from pylons.i18n.translation import _
-from pylons.middleware import  media_path
+from pylons.middleware import media_path
 
 from kallithea.lib.base import BaseController, render
 
@@ -50,7 +50,7 @@
     """
 
     def __before__(self):
-        #disable all base actions since we don't need them here
+        # disable all base actions since we don't need them here
         pass
 
     def document(self):
@@ -60,12 +60,16 @@
         log.debug('### %s ###' % (resp and resp.status or 'no response'))
 
         e = request.environ
-        c.serv_p = r'%(protocol)s://%(host)s/' \
-                                    % {'protocol': e.get('wsgi.url_scheme'),
-                                       'host': e.get('HTTP_HOST'), }
-
-        c.error_message = resp and cgi.escape(request.GET.get('code', str(resp.status)))
-        c.error_explanation = resp and self.get_error_explanation(resp.status_int)
+        c.serv_p = r'%(protocol)s://%(host)s/' % {
+            'protocol': e.get('wsgi.url_scheme'),
+            'host': e.get('HTTP_HOST'), }
+        if resp:
+            c.error_message = cgi.escape(request.GET.get('code',
+                                                         str(resp.status)))
+            c.error_explanation = self.get_error_explanation(resp.status_int)
+        else:
+            c.error_message = _('No response')
+            c.error_explanation = _('Unknown error')
 
         return render('/errors/error_document.html')
 
--- a/kallithea/controllers/journal.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/journal.py	Mon Jul 20 15:07:54 2015 +0200
@@ -108,11 +108,11 @@
         journal = self._get_journal_data(repos)
         if public:
             _link = h.canonical_url('public_journal_atom')
-            _desc = '%s %s %s' % (c.site_name, _('public journal'),
+            _desc = '%s %s %s' % (c.site_name, _('Public Journal'),
                                   'atom feed')
         else:
             _link = h.canonical_url('journal_atom')
-            _desc = '%s %s %s' % (c.site_name, _('journal'), 'atom feed')
+            _desc = '%s %s %s' % (c.site_name, _('Journal'), 'atom feed')
 
         feed = Atom1Feed(title=_desc,
                          link=_link,
@@ -150,11 +150,11 @@
         journal = self._get_journal_data(repos)
         if public:
             _link = h.canonical_url('public_journal_atom')
-            _desc = '%s %s %s' % (c.site_name, _('public journal'),
+            _desc = '%s %s %s' % (c.site_name, _('Public Journal'),
                                   'rss feed')
         else:
             _link = h.canonical_url('journal_atom')
-            _desc = '%s %s %s' % (c.site_name, _('journal'), 'rss feed')
+            _desc = '%s %s %s' % (c.site_name, _('Journal'), 'rss feed')
 
         feed = Rss201rev2Feed(title=_desc,
                          link=_link,
@@ -239,10 +239,7 @@
 
         def desc(desc):
             from pylons import tmpl_context as c
-            if c.visual.stylify_metatags:
-                return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
-            else:
-                return h.urlify_text(h.truncate(desc, 60))
+            return h.urlify_text(desc, truncate=60, stylize=c.visual.stylify_metatags)
 
         def repo_actions(repo_name):
             return _render('repo_actions', repo_name)
--- a/kallithea/controllers/login.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/login.py	Mon Jul 20 15:07:54 2015 +0200
@@ -28,7 +28,6 @@
 
 import logging
 import formencode
-import datetime
 import urlparse
 
 from formencode import htmlfill
@@ -39,9 +38,9 @@
 
 import kallithea.lib.helpers as h
 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
-from kallithea.lib.auth_modules import importplugin
-from kallithea.lib.base import BaseController, render
+from kallithea.lib.base import BaseController, log_in_user, render
 from kallithea.lib.exceptions import UserCreationError
+from kallithea.lib.utils2 import safe_str
 from kallithea.model.db import User, Setting
 from kallithea.model.forms import LoginForm, RegisterForm, PasswordResetForm
 from kallithea.model.user import UserModel
@@ -56,38 +55,10 @@
     def __before__(self):
         super(LoginController, self).__before__()
 
-    def _store_user_in_session(self, username, remember=False):
-        user = User.get_by_username(username, case_insensitive=True)
-        auth_user = AuthUser(user.user_id)
-        auth_user.set_authenticated()
-        cs = auth_user.get_cookie_store()
-        session['authuser'] = cs
-        user.update_lastlogin()
-        Session().commit()
-
-        # If they want to be remembered, update the cookie
-        if remember:
-            _year = (datetime.datetime.now() +
-                     datetime.timedelta(seconds=60 * 60 * 24 * 365))
-            session._set_cookie_expires(_year)
-
-        session.save()
-
-        log.info('user %s is now authenticated and stored in '
-                 'session, session attrs %s' % (username, cs))
-
-        # dumps session attrs back to cookie
-        session._update_cookie_out()
-        # we set new cookie
-        headers = None
-        if session.request['set_cookie']:
-            # send set-cookie headers back to response to update cookie
-            headers = [('Set-Cookie', session.request['cookie_out'])]
-        return headers
-
     def _validate_came_from(self, came_from):
+        """Return True if came_from is valid and can and should be used"""
         if not came_from:
-            return came_from
+            return False
 
         parsed = urlparse.urlparse(came_from)
         server_parsed = urlparse.urlparse(url.current())
@@ -95,36 +66,38 @@
         if parsed.scheme and parsed.scheme not in allowed_schemes:
             log.error('Suspicious URL scheme detected %s for url %s' %
                      (parsed.scheme, parsed))
-            came_from = url('home')
-        elif server_parsed.netloc != parsed.netloc:
+            return False
+        if server_parsed.netloc != parsed.netloc:
             log.error('Suspicious NETLOC detected %s for url %s server url '
                       'is: %s' % (parsed.netloc, parsed, server_parsed))
-            came_from = url('home')
-        return came_from
+            return False
+        return True
+
+    def _redirect_to_origin(self, origin):
+        '''redirect to the original page, preserving any get arguments given'''
+        request.GET.pop('came_from', None)
+        raise HTTPFound(location=url(origin, **request.GET))
 
     def index(self):
-        _default_came_from = url('home')
-        came_from = self._validate_came_from(request.GET.get('came_from'))
-        c.came_from = came_from or _default_came_from
+        c.came_from = safe_str(request.GET.get('came_from', ''))
+        if not self._validate_came_from(c.came_from):
+            c.came_from = url('home')
 
         not_default = self.authuser.username != User.DEFAULT_USER
-        ip_allowed = self.authuser.ip_allowed
+        ip_allowed = AuthUser.check_ip_allowed(self.authuser, self.ip_addr)
 
         # redirect if already logged in
         if self.authuser.is_authenticated and not_default and ip_allowed:
-            raise HTTPFound(location=c.came_from)
+            return self._redirect_to_origin(c.came_from)
 
         if request.POST:
             # import Login Form validator class
             login_form = LoginForm()
             try:
-                session.invalidate()
                 c.form_result = login_form.to_python(dict(request.POST))
                 # form checks for username/password, now we're authenticated
-                headers = self._store_user_in_session(
-                                        username=c.form_result['username'],
-                                        remember=c.form_result['remember'])
-                raise HTTPFound(location=c.came_from, headers=headers)
+                username = c.form_result['username']
+                user = User.get_by_username(username, case_insensitive=True)
             except formencode.Invalid, errors:
                 defaults = errors.value
                 # remove password from filling in form again
@@ -142,22 +115,11 @@
                 # with user creation, explanation should be provided in
                 # Exception itself
                 h.flash(e, 'error')
+            else:
+                log_in_user(user, c.form_result['remember'],
+                    is_external_auth=False)
+                return self._redirect_to_origin(c.came_from)
 
-        # check if we use container plugin, and try to login using it.
-        auth_plugins = Setting.get_auth_plugins()
-        if any((importplugin(name).is_container_auth for name in auth_plugins)):
-            from kallithea.lib import auth_modules
-            try:
-                auth_info = auth_modules.authenticate('', '', request.environ)
-            except UserCreationError, e:
-                log.error(e)
-                h.flash(e, 'error')
-                # render login, with flash message about limit
-                return render('/login.html')
-
-            if auth_info:
-                headers = self._store_user_in_session(auth_info.get('username'))
-                raise HTTPFound(location=c.came_from, headers=headers)
         return render('/login.html')
 
     @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
@@ -185,7 +147,7 @@
                                       remoteip=self.ip_addr)
                     if c.captcha_active and not response.is_valid:
                         _value = form_result
-                        _msg = _('bad captcha')
+                        _msg = _('Bad captcha')
                         error_dict = {'recaptcha_field': _msg}
                         raise formencode.Invalid(_msg, _value, None,
                                                  error_dict=error_dict)
@@ -231,7 +193,7 @@
                                       remoteip=self.ip_addr)
                     if c.captcha_active and not response.is_valid:
                         _value = form_result
-                        _msg = _('bad captcha')
+                        _msg = _('Bad captcha')
                         error_dict = {'recaptcha_field': _msg}
                         raise formencode.Invalid(_msg, _value, None,
                                                  error_dict=error_dict)
--- a/kallithea/controllers/pullrequests.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/pullrequests.py	Mon Jul 20 15:07:54 2015 +0200
@@ -44,6 +44,7 @@
 from kallithea.lib.helpers import Page
 from kallithea.lib import helpers as h
 from kallithea.lib import diffs
+from kallithea.lib.exceptions import UserInvalidException
 from kallithea.lib.utils import action_logger, jsonify
 from kallithea.lib.vcs.utils import safe_str
 from kallithea.lib.vcs.exceptions import EmptyRepositoryError
@@ -362,6 +363,9 @@
             Session().commit()
             h.flash(_('Successfully opened new pull request'),
                     category='success')
+        except UserInvalidException, u:
+            h.flash(_('Invalid reviewer "%s" specified') % u, category='error')
+            raise HTTPBadRequest()
         except Exception:
             h.flash(_('Error occurred while creating pull request'),
                     category='error')
@@ -446,6 +450,9 @@
                 old_pull_request.other_repo.repo_name, new_other_ref,
                 revisions, reviewers_ids, title, description
             )
+        except UserInvalidException, u:
+            h.flash(_('Invalid reviewer "%s" specified') % u, category='error')
+            raise HTTPBadRequest()
         except Exception:
             h.flash(_('Error occurred while creating pull request'),
                     category='error')
@@ -495,9 +502,12 @@
         old_description = pull_request.description
         pull_request.title = _form['pullrequest_title']
         pull_request.description = _form['pullrequest_desc'].strip() or _('No description')
-        PullRequestModel().mention_from_description(pull_request, old_description)
-
-        PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
+        try:
+            PullRequestModel().mention_from_description(pull_request, old_description)
+            PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
+        except UserInvalidException, u:
+            h.flash(_('Invalid reviewer "%s" specified') % u, category='error')
+            raise HTTPBadRequest()
 
         Session().commit()
         h.flash(_('Pull request updated'), category='success')
@@ -696,11 +706,11 @@
         if allowed_to_change_status:
             status = request.POST.get('changeset_status')
             close_pr = request.POST.get('save_close')
-        text = request.POST.get('text', '').strip() or _('No comments.')
+        text = request.POST.get('text', '').strip()
         if close_pr:
             text = _('Closing.') + '\n' + text
 
-        comm = ChangesetCommentsModel().create(
+        comment = ChangesetCommentsModel().create(
             text=text,
             repo=c.db_repo.repo_id,
             user=c.authuser.user_id,
@@ -723,7 +733,7 @@
                     c.db_repo.repo_id,
                     status,
                     c.authuser.user_id,
-                    comm,
+                    comment,
                     pull_request=pull_request_id
                 )
 
@@ -741,9 +751,9 @@
         data = {
            'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
         }
-        if comm:
-            c.co = comm
-            data.update(comm.get_dict())
+        if comment:
+            c.comment = comment
+            data.update(comment.get_dict())
             data.update({'rendered_text':
                          render('changeset/changeset_comment_block.html')})
 
--- a/kallithea/controllers/summary.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/controllers/summary.py	Mon Jul 20 15:07:54 2015 +0200
@@ -67,25 +67,6 @@
     def __before__(self):
         super(SummaryController, self).__before__()
 
-    def _get_download_links(self, repo):
-
-        download_l = []
-
-        branches_group = ([], _("Branches"))
-        tags_group = ([], _("Tags"))
-
-        for name, chs in c.db_repo_scm_instance.branches.items():
-            #chs = chs.split(':')[-1]
-            branches_group[0].append((chs, name),)
-        download_l.append(branches_group)
-
-        for name, chs in c.db_repo_scm_instance.tags.items():
-            #chs = chs.split(':')[-1]
-            tags_group[0].append((chs, name),)
-        download_l.append(tags_group)
-
-        return download_l
-
     def __get_readme_data(self, db_repo):
         repo_name = db_repo.repo_name
         log.debug('Looking for README file')
--- a/kallithea/i18n/be/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/be/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -1007,7 +1007,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr "Дададзены IP %s у белы спіс карыстача"
 
 #: kallithea/controllers/admin/users.py:488
@@ -1015,7 +1015,7 @@
 msgstr "Адбылася памылка пры захаванні IP"
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr "Выдалены IP %s з белага спісу карыстача"
 
 #: kallithea/lib/auth.py:745
@@ -2041,8 +2041,8 @@
 msgstr "Рэвізіі %(revs)s ужо ўключаны ў pull-request ці маюць усталяваны статус"
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
-msgstr "Калі ласка, увядзіце існы IPv4 ці IpV6 адрас"
+msgid "Please enter a valid IPv4 or IPv6 address"
+msgstr "Калі ласка, увядзіце існы IPv4 ці IPv6 адрас"
 
 #: kallithea/model/validators.py:818
 #, python-format
@@ -3117,7 +3117,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr "Новы IP-адрас"
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/cs/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/cs/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -998,7 +998,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:488
@@ -1006,7 +1006,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr ""
 
 #: kallithea/lib/auth.py:745
@@ -2005,7 +2005,7 @@
 msgstr ""
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
 #: kallithea/model/validators.py:818
@@ -3070,7 +3070,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/de/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/de/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -1024,7 +1024,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr "Die IP-Adresse %s wurde zur Nutzerwhitelist hinzugefügt"
 
 #: kallithea/controllers/admin/users.py:488
@@ -1032,7 +1032,7 @@
 msgstr "Während des Speicherns der IP-Adresse ist ein Fehler aufgetreten"
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr "IP-Adresse wurde von der Nutzerwhitelist entfernt"
 
 #: kallithea/lib/auth.py:745
@@ -2067,7 +2067,7 @@
 "haben den Status"
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "Bitte eine gültige IPv4- oder IPv6-Adresse angeben"
 
 #: kallithea/model/validators.py:818
@@ -3153,7 +3153,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr "Neue IP-Adresse"
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/fr/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/fr/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -1025,7 +1025,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr "L'adresse IP %s a été ajoutée à la liste blanche"
 
 #: kallithea/controllers/admin/users.py:488
@@ -1033,7 +1033,7 @@
 msgstr "Une erreur est survenue durant la sauvegarde d'IP"
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr "L'adresse IP a été supprimée de la liste blanche"
 
 #: kallithea/lib/auth.py:745
@@ -2059,7 +2059,7 @@
 "statuts définis"
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "Veuillez entrer une adresse IPv4 ou IPv6 valide"
 
 #: kallithea/model/validators.py:818
@@ -3137,7 +3137,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr "Nouvelle adresse IP"
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/hu/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/hu/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -997,7 +997,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:488
@@ -1005,7 +1005,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr ""
 
 #: kallithea/lib/auth.py:745
@@ -1998,7 +1998,7 @@
 msgstr ""
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
 #: kallithea/model/validators.py:818
@@ -3059,7 +3059,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/ja/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/ja/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -1009,7 +1009,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr "ユーザーホワイトリストにIP %s を追加しました"
 
 #: kallithea/controllers/admin/users.py:488
@@ -1017,7 +1017,7 @@
 msgstr "IPアドレスの保存中にエラーが発生しました"
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr "ユーザーホワイトリストからIPアドレスを削除しました"
 
 #: kallithea/lib/auth.py:745
@@ -2011,7 +2011,7 @@
 msgstr "リビジョン %(revs)s はすでにプルリクエストの一部かステータスが設定されています"
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "有効なIPv4かIPv6のアドレスを入力してください"
 
 #: kallithea/model/validators.py:818
@@ -3092,7 +3092,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr "新しいIPアドレス"
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/kallithea.pot	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/kallithea.pot	Mon Jul 20 15:07:54 2015 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.2.2\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2015-07-12 18:32+0200\n"
+"POT-Creation-Date: 2015-07-14 11:48+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -16,7 +16,7 @@
 "Content-Transfer-Encoding: 8bit\n"
 
 #: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:449
+#: kallithea/controllers/pullrequests.py:248 kallithea/lib/base.py:463
 msgid "There are no changesets yet"
 msgstr ""
 
@@ -24,7 +24,11 @@
 #: kallithea/controllers/admin/permissions.py:62
 #: kallithea/controllers/admin/permissions.py:66
 #: kallithea/controllers/admin/permissions.py:70
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
+#: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr ""
 
@@ -43,7 +47,7 @@
 
 #: kallithea/controllers/changeset.py:169
 #, python-format
-msgid "increase diff context to %(num)s lines"
+msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
 #: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:97
@@ -51,40 +55,43 @@
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:352
-#: kallithea/controllers/pullrequests.py:699
-msgid "No comments."
-msgstr ""
-
 #: kallithea/controllers/changeset.py:382
 msgid "Changing status on a changeset associated with a closed pull request is not allowed"
 msgstr ""
 
-#: kallithea/controllers/compare.py:158 kallithea/templates/base/root.html:42
+#: kallithea/controllers/compare.py:162 kallithea/templates/base/root.html:42
 msgid "Select changeset"
 msgstr ""
 
-#: kallithea/controllers/compare.py:255
+#: kallithea/controllers/compare.py:259
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
-#: kallithea/controllers/error.py:96
+#: kallithea/controllers/error.py:71
+msgid "No response"
+msgstr ""
+
+#: kallithea/controllers/error.py:72
+msgid "Unknown error"
+msgstr ""
+
+#: kallithea/controllers/error.py:100
 msgid "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 
-#: kallithea/controllers/error.py:99
-msgid "Unauthorized access to resource"
-msgstr ""
-
-#: kallithea/controllers/error.py:101
-msgid "You don't have permission to view this page"
-msgstr ""
-
 #: kallithea/controllers/error.py:103
-msgid "The resource could not be found"
+msgid "Unauthorized access to resource"
 msgstr ""
 
 #: kallithea/controllers/error.py:105
+msgid "You don't have permission to view this page"
+msgstr ""
+
+#: kallithea/controllers/error.py:107
+msgid "The resource could not be found"
+msgstr ""
+
+#: kallithea/controllers/error.py:109
 msgid "The server encountered an unexpected condition which prevented it from fulfilling the request."
 msgstr ""
 
@@ -103,8 +110,8 @@
 #: kallithea/templates/changeset/changeset.html:166
 #: kallithea/templates/compare/compare_diff.html:78
 #: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:328
-#: kallithea/templates/pullrequests/pullrequest_show.html:351
+#: kallithea/templates/pullrequests/pullrequest_show.html:330
+#: kallithea/templates/pullrequests/pullrequest_show.html:354
 msgid "Changeset was too big and was cut off..."
 msgstr ""
 
@@ -210,16 +217,14 @@
 msgid "Changesets"
 msgstr ""
 
-#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:182
-#: kallithea/controllers/summary.py:74 kallithea/model/scm.py:816
-#: kallithea/templates/switch_to_list.html:3
+#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:183
+#: kallithea/model/scm.py:816 kallithea/templates/switch_to_list.html:3
 #: kallithea/templates/branches/branches.html:10
 msgid "Branches"
 msgstr ""
 
-#: kallithea/controllers/files.py:776 kallithea/controllers/pullrequests.py:183
-#: kallithea/controllers/summary.py:75 kallithea/model/scm.py:827
-#: kallithea/templates/switch_to_list.html:25
+#: kallithea/controllers/files.py:776 kallithea/controllers/pullrequests.py:184
+#: kallithea/model/scm.py:827 kallithea/templates/switch_to_list.html:25
 #: kallithea/templates/tags/tags.html:10
 msgid "Tags"
 msgstr ""
@@ -262,141 +267,153 @@
 msgstr ""
 
 #: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
-msgid "public journal"
+#: kallithea/templates/journal/public_journal.html:4
+#: kallithea/templates/journal/public_journal.html:21
+msgid "Public Journal"
 msgstr ""
 
 #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-msgid "journal"
-msgstr ""
-
-#: kallithea/controllers/login.py:188 kallithea/controllers/login.py:234
-msgid "bad captcha"
-msgstr ""
-
-#: kallithea/controllers/login.py:194
+#: kallithea/templates/base/base.html:229
+#: kallithea/templates/journal/journal.html:4
+#: kallithea/templates/journal/journal.html:12
+msgid "Journal"
+msgstr ""
+
+#: kallithea/controllers/login.py:191 kallithea/controllers/login.py:237
+msgid "Bad captcha"
+msgstr ""
+
+#: kallithea/controllers/login.py:197
 msgid "You have successfully registered into Kallithea"
 msgstr ""
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:242
 msgid "Your password reset link was sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:260
+#: kallithea/controllers/login.py:263
 msgid "Your password reset was successful, new password has been sent to your email"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:130
+#: kallithea/controllers/pullrequests.py:131
 #, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:159
 #: kallithea/templates/changeset/changeset.html:12
 #: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:180
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:181
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:822
+#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:822
 #: kallithea/templates/switch_to_list.html:38
 #: kallithea/templates/bookmarks/bookmarks.html:10
 msgid "Bookmarks"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:312
+#: kallithea/controllers/pullrequests.py:313
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:497
+#: kallithea/controllers/pullrequests.py:357
+#: kallithea/controllers/pullrequests.py:504
 msgid "No description"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:363
+#: kallithea/controllers/pullrequests.py:364
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:450
+#: kallithea/controllers/pullrequests.py:367
+#: kallithea/controllers/pullrequests.py:454
+#: kallithea/controllers/pullrequests.py:509
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:370
+#: kallithea/controllers/pullrequests.py:457
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:398
+#: kallithea/controllers/pullrequests.py:402
 msgid "Missing changesets since the previous pull request:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:409
 #, python-format
 msgid "New changesets on %s %s since the previous pull request:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:412
+#: kallithea/controllers/pullrequests.py:416
 msgid "Ancestor didn't change - show diff since previous version:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:419
+#: kallithea/controllers/pullrequests.py:423
 #, python-format
 msgid "This pull request is based on another %s revision and there is no simple diff."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
+#: kallithea/controllers/pullrequests.py:425
 #, python-format
 msgid "No changes found on %s %s since previous version."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:456
+#: kallithea/controllers/pullrequests.py:463
 #, python-format
 msgid "Closed, replaced by %s ."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:464
+#: kallithea/controllers/pullrequests.py:471
 msgid "Pull request update created"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:503
+#: kallithea/controllers/pullrequests.py:513
 msgid "Pull request updated"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:518
+#: kallithea/controllers/pullrequests.py:528
 msgid "Successfully deleted pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:577
+#: kallithea/controllers/pullrequests.py:587
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:579
+#: kallithea/controllers/pullrequests.py:589
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:597
+#: kallithea/controllers/pullrequests.py:607
 #, python-format
 msgid "This pull request can be updated with changes on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:600
+#: kallithea/controllers/pullrequests.py:610
 msgid "No changesets found for updating this pull request."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:608
+#: kallithea/controllers/pullrequests.py:618
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:614
+#: kallithea/controllers/pullrequests.py:624
 msgid "Git pull requests don't support updates yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:701
+#: kallithea/controllers/pullrequests.py:711
 msgid "Closing."
 msgstr ""
 
@@ -412,13 +429,13 @@
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:199
-#: kallithea/templates/summary/summary.html:388
+#: kallithea/controllers/summary.py:180
+#: kallithea/templates/summary/summary.html:384
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:202
-#: kallithea/templates/summary/summary.html:102
+#: kallithea/controllers/summary.py:183
+#: kallithea/templates/summary/summary.html:98
 msgid "Statistics are disabled for this repository"
 msgstr ""
 
@@ -440,37 +457,37 @@
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:288
-msgid "forever"
+#: kallithea/controllers/admin/users.py:287
+msgid "Forever"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:239
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/users.py:288
 msgid "5 minutes"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:240
-#: kallithea/controllers/admin/users.py:290
+#: kallithea/controllers/admin/users.py:289
 msgid "1 hour"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:241
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/users.py:290
 msgid "1 day"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:242
-#: kallithea/controllers/admin/users.py:292
+#: kallithea/controllers/admin/users.py:291
 msgid "1 month"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:294
+#: kallithea/controllers/admin/users.py:293
 msgid "Lifetime"
 msgstr ""
 
@@ -484,7 +501,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:233
-msgid "unmodified"
+msgid "Unmodified"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:262
@@ -509,7 +526,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:143
-#: kallithea/controllers/admin/users.py:206
+#: kallithea/controllers/admin/users.py:208
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
@@ -523,45 +540,53 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:431
+#: kallithea/controllers/admin/users.py:417
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:221
-#: kallithea/controllers/admin/users.py:437
+#: kallithea/controllers/admin/users.py:423
 msgid "An error occurred during email saving"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:230
-#: kallithea/controllers/admin/users.py:448
+#: kallithea/controllers/admin/users.py:435
 msgid "Removed email from user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:254
-#: kallithea/controllers/admin/users.py:314
-msgid "Api key successfully created"
+#: kallithea/controllers/admin/users.py:310
+msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:266
-#: kallithea/controllers/admin/users.py:330
-msgid "Api key successfully reset"
+#: kallithea/controllers/admin/users.py:323
+msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:270
-#: kallithea/controllers/admin/users.py:334
-msgid "Api key successfully deleted"
+#: kallithea/controllers/admin/users.py:327
+msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:63
 #: kallithea/controllers/admin/permissions.py:67
 #: kallithea/controllers/admin/permissions.py:71
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
+#: kallithea/templates/base/perms_summary.html:15
 msgid "Read"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
+#: kallithea/templates/base/perms_summary.html:16
 msgid "Write"
 msgstr ""
 
@@ -573,13 +598,16 @@
 #: kallithea/templates/admin/permissions/permissions.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_groups.html:10
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_groups.html:10
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
@@ -590,6 +618,7 @@
 #: kallithea/templates/base/base.html:260
 #: kallithea/templates/base/base.html:266
 #: kallithea/templates/base/base.html:267
+#: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr ""
 
@@ -599,6 +628,7 @@
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
+#: kallithea/templates/admin/auth/auth_settings.html:40
 msgid "Disabled"
 msgstr ""
 
@@ -618,7 +648,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1684
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
 msgid "Manual activation of external account"
 msgstr ""
 
@@ -630,7 +660,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1685
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
 msgid "Automatic activation of external account"
 msgstr ""
 
@@ -639,6 +669,7 @@
 #: kallithea/controllers/admin/permissions.py:96
 #: kallithea/controllers/admin/permissions.py:99
 #: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:40
 msgid "Enabled"
 msgstr ""
 
@@ -935,79 +966,76 @@
 msgstr ""
 
 #: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:396
+#: kallithea/controllers/admin/users.py:386
 msgid "Updated permissions"
 msgstr ""
 
 #: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:400
+#: kallithea/controllers/admin/users.py:390
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:132
+#: kallithea/controllers/admin/users.py:133
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:147
+#: kallithea/controllers/admin/users.py:148
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:186
+#: kallithea/controllers/admin/users.py:188
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:222
+#: kallithea/controllers/admin/users.py:224
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:227
+#: kallithea/controllers/admin/users.py:229
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:241
-#: kallithea/controllers/admin/users.py:259
-#: kallithea/controllers/admin/users.py:282
-#: kallithea/controllers/admin/users.py:307
-#: kallithea/controllers/admin/users.py:320
-#: kallithea/controllers/admin/users.py:344
-#: kallithea/controllers/admin/users.py:407
-#: kallithea/controllers/admin/users.py:454
-msgid "You can't edit this user"
-msgstr ""
-
-#: kallithea/controllers/admin/users.py:482
-#, python-format
-msgid "Added ip %s to user whitelist"
-msgstr ""
-
-#: kallithea/controllers/admin/users.py:488
+#: kallithea/controllers/admin/users.py:242
+msgid "The default user cannot be edited"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:465
+#, python-format
+msgid "Added IP address %s to user whitelist"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:471
 msgid "An error occurred during ip saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
-msgstr ""
-
-#: kallithea/lib/auth.py:746
+#: kallithea/controllers/admin/users.py:485
+msgid "Removed IP address from user whitelist"
+msgstr ""
+
+#: kallithea/lib/auth.py:747
 #, python-format
 msgid "IP %s not allowed"
 msgstr ""
 
-#: kallithea/lib/auth.py:814
+#: kallithea/lib/auth.py:760
+msgid "Invalid API key"
+msgstr ""
+
+#: kallithea/lib/auth.py:798
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: kallithea/lib/auth.py:851
+#: kallithea/lib/auth.py:830
 msgid "You need to be signed in to view this page"
 msgstr ""
 
-#: kallithea/lib/base.py:427
+#: kallithea/lib/base.py:441
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:453 kallithea/lib/helpers.py:626
+#: kallithea/lib/base.py:467 kallithea/lib/helpers.py:644
 msgid "Changeset not found"
 msgstr ""
 
@@ -1023,225 +1051,225 @@
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:610
+#: kallithea/lib/helpers.py:628
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:613
+#: kallithea/lib/helpers.py:631
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:676
+#: kallithea/lib/helpers.py:694
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:682
-msgid "compare view"
-msgstr ""
-
-#: kallithea/lib/helpers.py:701
+#: kallithea/lib/helpers.py:700
+msgid "Compare view"
+msgstr ""
+
+#: kallithea/lib/helpers.py:719
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:702
+#: kallithea/lib/helpers.py:720
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:703 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:721 kallithea/templates/changelog/changelog.html:44
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:727
-#, python-format
-msgid "fork name %s"
-msgstr ""
-
-#: kallithea/lib/helpers.py:744
-#, python-format
-msgid "Pull request #%s"
-msgstr ""
-
-#: kallithea/lib/helpers.py:754
+#: kallithea/lib/helpers.py:745
+#, python-format
+msgid "Fork name %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:765
+#, python-format
+msgid "Pull request %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:775
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:756 kallithea/lib/helpers.py:768
+#: kallithea/lib/helpers.py:777 kallithea/lib/helpers.py:789
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:758
+#: kallithea/lib/helpers.py:779
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770
+#: kallithea/lib/helpers.py:781 kallithea/lib/helpers.py:791
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
+#: kallithea/lib/helpers.py:783 kallithea/lib/helpers.py:793
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:764
+#: kallithea/lib/helpers.py:785
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:787
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:774
+#: kallithea/lib/helpers.py:795
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:776
+#: kallithea/lib/helpers.py:797
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:778
+#: kallithea/lib/helpers.py:799
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:780
+#: kallithea/lib/helpers.py:801
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:782
+#: kallithea/lib/helpers.py:803
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:784
+#: kallithea/lib/helpers.py:805
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:786
+#: kallithea/lib/helpers.py:807
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:788
+#: kallithea/lib/helpers.py:809
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:790
+#: kallithea/lib/helpers.py:811
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:792
+#: kallithea/lib/helpers.py:813
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:794
+#: kallithea/lib/helpers.py:815
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:796
+#: kallithea/lib/helpers.py:817
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:798
+#: kallithea/lib/helpers.py:819
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1127
+#: kallithea/lib/helpers.py:1148
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1131
+#: kallithea/lib/helpers.py:1152
 msgid "No Files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1197
+#: kallithea/lib/helpers.py:1218
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1200
+#: kallithea/lib/helpers.py:1221
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1203
+#: kallithea/lib/helpers.py:1224
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1206
+#: kallithea/lib/helpers.py:1227
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1211
+#: kallithea/lib/helpers.py:1232
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1443
+#: kallithea/lib/helpers.py:1468
 #, python-format
 msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories"
 msgstr ""
 
-#: kallithea/lib/utils2.py:425
+#: kallithea/lib/utils2.py:415
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:426
+#: kallithea/lib/utils2.py:416
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:427
+#: kallithea/lib/utils2.py:417
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:428
+#: kallithea/lib/utils2.py:418
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:429
+#: kallithea/lib/utils2.py:419
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:430
+#: kallithea/lib/utils2.py:420
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:436
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:448
+#: kallithea/lib/utils2.py:438
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:450
+#: kallithea/lib/utils2.py:440
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:453
+#: kallithea/lib/utils2.py:443
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:456
+#: kallithea/lib/utils2.py:446
 msgid "just now"
 msgstr ""
 
@@ -1256,7 +1284,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649 kallithea/model/db.py:1651
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
 msgid "Repository no access"
 msgstr ""
 
@@ -1271,7 +1299,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650 kallithea/model/db.py:1652
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
 msgid "Repository read access"
 msgstr ""
 
@@ -1286,7 +1314,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651 kallithea/model/db.py:1653
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
 msgid "Repository write access"
 msgstr ""
 
@@ -1301,7 +1329,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652 kallithea/model/db.py:1654
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
 msgid "Repository admin access"
 msgstr ""
 
@@ -1340,7 +1368,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1649
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
 msgid "Kallithea Administrator"
 msgstr ""
 
@@ -1355,7 +1383,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670 kallithea/model/db.py:1672
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
 msgid "Repository creation disabled"
 msgstr ""
 
@@ -1370,7 +1398,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671 kallithea/model/db.py:1673
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
 msgid "Repository creation enabled"
 msgstr ""
 
@@ -1385,7 +1413,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675 kallithea/model/db.py:1677
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
 msgid "Repository forking disabled"
 msgstr ""
 
@@ -1400,7 +1428,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676 kallithea/model/db.py:1678
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
 msgid "Repository forking enabled"
 msgstr ""
 
@@ -1436,7 +1464,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200 kallithea/model/db.py:2202
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200 kallithea/model/db.py:2228
 msgid "Not Reviewed"
 msgstr ""
 
@@ -1451,7 +1479,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2203
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229
 msgid "Approved"
 msgstr ""
 
@@ -1466,7 +1494,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2204
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230
 msgid "Rejected"
 msgstr ""
 
@@ -1481,7 +1509,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203 kallithea/model/db.py:2205
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203 kallithea/model/db.py:2231
 msgid "Under Review"
 msgstr ""
 
@@ -1493,7 +1521,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1500
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498
 msgid "top level"
 msgstr ""
 
@@ -1505,7 +1533,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654 kallithea/model/db.py:1656
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
 msgid "Repository group no access"
 msgstr ""
 
@@ -1517,7 +1545,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655 kallithea/model/db.py:1657
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
 msgid "Repository group read access"
 msgstr ""
 
@@ -1529,7 +1557,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656 kallithea/model/db.py:1658
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
 msgid "Repository group write access"
 msgstr ""
 
@@ -1541,7 +1569,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657 kallithea/model/db.py:1659
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
 msgid "Repository group admin access"
 msgstr ""
 
@@ -1552,7 +1580,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659 kallithea/model/db.py:1661
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
 msgid "User group no access"
 msgstr ""
 
@@ -1563,7 +1591,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660 kallithea/model/db.py:1662
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
 msgid "User group read access"
 msgstr ""
 
@@ -1574,7 +1602,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661 kallithea/model/db.py:1663
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
 msgid "User group write access"
 msgstr ""
 
@@ -1585,7 +1613,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662 kallithea/model/db.py:1664
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
 msgid "User group admin access"
 msgstr ""
 
@@ -1596,7 +1624,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664 kallithea/model/db.py:1666
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
 msgid "Repository Group creation disabled"
 msgstr ""
 
@@ -1607,7 +1635,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665 kallithea/model/db.py:1667
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
 msgid "Repository Group creation enabled"
 msgstr ""
 
@@ -1618,7 +1646,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667 kallithea/model/db.py:1669
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
 msgid "User Group creation disabled"
 msgstr ""
 
@@ -1629,7 +1657,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668 kallithea/model/db.py:1670
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
 msgid "User Group creation enabled"
 msgstr ""
 
@@ -1640,7 +1668,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1680
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
 msgid "Registration disabled"
 msgstr ""
 
@@ -1651,7 +1679,7 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679 kallithea/model/db.py:1681
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679 kallithea/model/db.py:1699
 msgid "User Registration with manual account activation"
 msgstr ""
 
@@ -1662,29 +1690,113 @@
 #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680 kallithea/model/db.py:1682
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680 kallithea/model/db.py:1700
 msgid "User Registration with automatic account activation"
 msgstr ""
 
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1674
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
 msgid "Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1675
+#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
 msgid "Repository creation disabled with write permission to a repository group"
 msgstr ""
 
-#: kallithea/model/comment.py:76
+#: kallithea/model/comment.py:72
 #, python-format
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:231 kallithea/model/pull_request.py:165
+#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
 msgid "[Mention]"
 msgstr ""
 
+#: kallithea/model/db.py:1517
+msgid "Top level"
+msgstr ""
+
+#: kallithea/model/db.py:1668
+msgid "Default user has no access to new Repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1669
+msgid "Default user has read access to new Repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1670
+msgid "Default user has write access to new Repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1671
+msgid "Default user has admin access to new Repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1673
+msgid "Default user has no access to new Repository Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1674
+msgid "Default user has read access to new Repository Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1675
+msgid "Default user has write access to new Repository Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1676
+msgid "Default user has admin access to new Repository Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1678
+msgid "Default user has no access to new User Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1679
+msgid "Default user has read access to new User Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1680
+msgid "Default user has write access to new User Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1681
+msgid "Default user has admin access to new User Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1683
+msgid "Only admins can create Repository Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1684
+msgid "Non-admins can create Repository Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1686
+msgid "Only admins can create User Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1687
+msgid "Non-admins can create User Groups"
+msgstr ""
+
+#: kallithea/model/db.py:1689
+msgid "Only admins can create top level Repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1690
+msgid "Non-admins can create top level Repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1695
+msgid "Only admins can fork repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1696
+msgid "Non-admins can can fork repositories"
+msgstr ""
+
 #: kallithea/model/forms.py:57
 msgid "Please enter a login"
 msgstr ""
@@ -1707,63 +1819,93 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:252
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:253
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr ""
-
 #: kallithea/model/notification.py:254
 #, python-format
-msgid "%(user)s mentioned you at %(when)s"
+msgid "%(user)s commented on changeset %(age)s"
 msgstr ""
 
 #: kallithea/model/notification.py:255
 #, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
+msgid "%(user)s sent message %(age)s"
 msgstr ""
 
 #: kallithea/model/notification.py:256
 #, python-format
-msgid "%(user)s opened new pull request at %(when)s"
+msgid "%(user)s mentioned you %(age)s"
 msgstr ""
 
 #: kallithea/model/notification.py:257
 #, python-format
+msgid "%(user)s registered in Kallithea %(age)s"
+msgstr ""
+
+#: kallithea/model/notification.py:258
+#, python-format
+msgid "%(user)s opened new pull request %(age)s"
+msgstr ""
+
+#: kallithea/model/notification.py:259
+#, python-format
+msgid "%(user)s commented on pull request %(age)s"
+msgstr ""
+
+#: kallithea/model/notification.py:266
+#, python-format
+msgid "%(user)s commented on changeset at %(when)s"
+msgstr ""
+
+#: kallithea/model/notification.py:267
+#, python-format
+msgid "%(user)s sent message at %(when)s"
+msgstr ""
+
+#: kallithea/model/notification.py:268
+#, python-format
+msgid "%(user)s mentioned you at %(when)s"
+msgstr ""
+
+#: kallithea/model/notification.py:269
+#, python-format
+msgid "%(user)s registered in Kallithea at %(when)s"
+msgstr ""
+
+#: kallithea/model/notification.py:270
+#, python-format
+msgid "%(user)s opened new pull request at %(when)s"
+msgstr ""
+
+#: kallithea/model/notification.py:271
+#, python-format
 msgid "%(user)s commented on pull request at %(when)s"
 msgstr ""
 
-#: kallithea/model/notification.py:296
-#, python-format
-msgid "Comment on %(repo_name)s changeset %(short_id)s on %(branch)s by %(comment_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:299
-#, python-format
-msgid "New user %(new_username)s registered"
-msgstr ""
-
-#: kallithea/model/notification.py:301
-#, python-format
-msgid "Review request on %(repo_name)s pull request #%(pr_id)s from %(ref)s by %(pr_username)s"
-msgstr ""
-
 #: kallithea/model/notification.py:302
 #, python-format
-msgid "Comment on %(repo_name)s pull request #%(pr_id)s from %(ref)s by %(comment_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:315
+msgid "[Comment from %(comment_username)s] %(repo_name)s changeset %(short_id)s on %(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:305
+#, python-format
+msgid "New user %(new_username)s registered"
+msgstr ""
+
+#: kallithea/model/notification.py:307
+#, python-format
+msgid "[Added by %(pr_username)s] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+msgstr ""
+
+#: kallithea/model/notification.py:308
+#, python-format
+msgid "[Comment from %(comment_username)s] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+msgstr ""
+
+#: kallithea/model/notification.py:321
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:133
-#, python-format
-msgid "%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s"
+#: kallithea/model/pull_request.py:137
+#, python-format
+msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
 #: kallithea/model/scm.py:808
@@ -1774,15 +1916,15 @@
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:214 kallithea/model/user.py:236
+#: kallithea/model/user.py:217 kallithea/model/user.py:239
 msgid "You can't Edit this user since it's crucial for entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:258
 msgid "You can't remove this user since it's crucial for entire application"
 msgstr ""
 
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:263
 #, python-format
 msgid "User \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories: %s"
 msgstr ""
@@ -1913,11 +2055,11 @@
 msgstr ""
 
 #: kallithea/model/validators.py:474
-msgid "invalid clone URL"
+msgid "Invalid repository URL"
 msgstr ""
 
 #: kallithea/model/validators.py:475
-msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
+msgid "Invalid repository URL. It must be a valid http, https, ssh, svn+http or svn+https URL"
 msgstr ""
 
 #: kallithea/model/validators.py:500
@@ -1963,7 +2105,7 @@
 msgstr ""
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
 #: kallithea/model/validators.py:818
@@ -2041,13 +2183,13 @@
 msgstr ""
 
 #: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:131
+#: kallithea/templates/index_base.html:127
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:64
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:42
 #: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
 #: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:72
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 #: kallithea/templates/admin/repos/repos.html:48
 #: kallithea/templates/admin/user_groups/user_group_add.html:40
 #: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
@@ -2063,7 +2205,7 @@
 msgid "Description"
 msgstr ""
 
-#: kallithea/templates/index_base.html:129
+#: kallithea/templates/index_base.html:125
 #: kallithea/templates/admin/my_account/my_account_repos.html:46
 #: kallithea/templates/admin/my_account/my_account_watched.html:46
 #: kallithea/templates/admin/repo_groups/repo_groups.html:46
@@ -2084,11 +2226,11 @@
 msgid "Name"
 msgstr ""
 
-#: kallithea/templates/index_base.html:132
+#: kallithea/templates/index_base.html:128
 msgid "Last Change"
 msgstr ""
 
-#: kallithea/templates/index_base.html:134
+#: kallithea/templates/index_base.html:130
 #: kallithea/templates/admin/my_account/my_account_repos.html:48
 #: kallithea/templates/admin/my_account/my_account_watched.html:48
 #: kallithea/templates/admin/repos/repos.html:49
@@ -2097,18 +2239,19 @@
 msgid "Tip"
 msgstr ""
 
-#: kallithea/templates/index_base.html:136
+#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
 #: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/admin/repos/repo_edit_settings.html:62
 #: kallithea/templates/admin/repos/repos.html:50
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
 #: kallithea/templates/admin/user_groups/user_groups.html:50
-#: kallithea/templates/summary/summary.html:138
+#: kallithea/templates/pullrequests/pullrequest_show.html:226
+#: kallithea/templates/summary/summary.html:134
 msgid "Owner"
 msgstr ""
 
-#: kallithea/templates/index_base.html:144
+#: kallithea/templates/index_base.html:140
 #: kallithea/templates/admin/my_account/my_account_repos.html:57
 #: kallithea/templates/admin/my_account/my_account_watched.html:57
 #: kallithea/templates/base/root.html:44
@@ -2120,7 +2263,7 @@
 msgid "Click to sort ascending"
 msgstr ""
 
-#: kallithea/templates/index_base.html:145
+#: kallithea/templates/index_base.html:141
 #: kallithea/templates/admin/my_account/my_account_repos.html:58
 #: kallithea/templates/admin/my_account/my_account_watched.html:58
 #: kallithea/templates/base/root.html:45
@@ -2132,11 +2275,11 @@
 msgid "Click to sort descending"
 msgstr ""
 
-#: kallithea/templates/index_base.html:146
+#: kallithea/templates/index_base.html:142
 msgid "No repositories found."
 msgstr ""
 
-#: kallithea/templates/index_base.html:147
+#: kallithea/templates/index_base.html:143
 #: kallithea/templates/admin/my_account/my_account_repos.html:60
 #: kallithea/templates/admin/my_account/my_account_watched.html:60
 #: kallithea/templates/base/root.html:47
@@ -2148,7 +2291,7 @@
 msgid "Data error."
 msgstr ""
 
-#: kallithea/templates/index_base.html:148
+#: kallithea/templates/index_base.html:144
 #: kallithea/templates/admin/my_account/my_account_repos.html:61
 #: kallithea/templates/admin/my_account/my_account_watched.html:61
 #: kallithea/templates/base/base.html:147 kallithea/templates/base/root.html:48
@@ -2170,7 +2313,7 @@
 msgid "Log In to %s"
 msgstr ""
 
-#: kallithea/templates/login.html:27 kallithea/templates/register.html:24
+#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
 #: kallithea/templates/admin/my_account/my_account_profile.html:32
 #: kallithea/templates/admin/users/user_add.html:32
@@ -2180,29 +2323,29 @@
 msgid "Username"
 msgstr ""
 
-#: kallithea/templates/login.html:36 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
+#: kallithea/templates/admin/my_account/my_account.html:37
 #: kallithea/templates/admin/users/user_add.html:41
 #: kallithea/templates/base/base.html:318
 msgid "Password"
 msgstr ""
 
-#: kallithea/templates/login.html:46
+#: kallithea/templates/login.html:44
 msgid "Remember me"
 msgstr ""
 
-#: kallithea/templates/login.html:50
+#: kallithea/templates/login.html:53
+msgid "Forgot your password ?"
+msgstr ""
+
+#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:329
+msgid "Don't have an account ?"
+msgstr ""
+
+#: kallithea/templates/login.html:59
 msgid "Sign In"
 msgstr ""
 
-#: kallithea/templates/login.html:56
-msgid "Forgot your password ?"
-msgstr ""
-
-#: kallithea/templates/login.html:59 kallithea/templates/base/base.html:329
-msgid "Don't have an account ?"
-msgstr ""
-
 #: kallithea/templates/password_reset.html:5
 msgid "Password Reset"
 msgstr ""
@@ -2382,16 +2525,6 @@
 msgid "Available built-in plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:40
-#: kallithea/templates/base/root.html:40
-msgid "enabled"
-msgstr ""
-
-#: kallithea/templates/admin/auth/auth_settings.html:40
-#: kallithea/templates/base/root.html:41
-msgid "disabled"
-msgstr ""
-
 #: kallithea/templates/admin/auth/auth_settings.html:51
 msgid "Plugin"
 msgstr ""
@@ -2400,12 +2533,12 @@
 #: kallithea/templates/admin/defaults/defaults.html:82
 #: kallithea/templates/admin/my_account/my_account_password.html:33
 #: kallithea/templates/admin/my_account/my_account_profile.html:70
-#: kallithea/templates/admin/permissions/permissions_globals.html:108
+#: kallithea/templates/admin/permissions/permissions_globals.html:112
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:69
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
 #: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:134
+#: kallithea/templates/admin/repos/repo_edit_settings.html:136
 #: kallithea/templates/admin/settings/settings_hooks.html:53
 #: kallithea/templates/admin/user_groups/user_group_add.html:57
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
@@ -2417,62 +2550,59 @@
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:5
-msgid "Repository Defaults"
-msgstr ""
-
 #: kallithea/templates/admin/defaults/defaults.html:11
 #: kallithea/templates/base/base.html:66
-msgid "Defaults"
+msgid "Repository Defaults"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:55
 #: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Type"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:82
+#: kallithea/templates/admin/repos/repo_add_base.html:73
+#: kallithea/templates/admin/repos/repo_edit_settings.html:84
 #: kallithea/templates/data_table/_dt_elements.html:72
 msgid "Private repository"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_settings.html:86
+#: kallithea/templates/admin/repos/repo_add_base.html:77
+#: kallithea/templates/admin/repos/repo_edit_settings.html:88
 #: kallithea/templates/forks/fork.html:72
 msgid "Private repositories are only visible to people explicitly added as collaborators."
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:91
+#: kallithea/templates/admin/repos/repo_edit_settings.html:93
 msgid "Enable statistics"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:95
+#: kallithea/templates/admin/repos/repo_edit_settings.html:97
 msgid "Enable statistics window on summary page."
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:100
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
 msgid "Enable downloads"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:104
+#: kallithea/templates/admin/repos/repo_edit_settings.html:106
 msgid "Enable download menu on summary page."
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:73
 #: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:109
+#: kallithea/templates/admin/repos/repo_edit_settings.html:111
 msgid "Enable locking"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:113
+#: kallithea/templates/admin/repos/repo_edit_settings.html:115
 msgid "Enable lock-by-pulling on repository."
 msgstr ""
 
@@ -2502,6 +2632,12 @@
 #: kallithea/templates/admin/gists/index.html:59
 #: kallithea/templates/admin/gists/show.html:47
 #: kallithea/templates/admin/gists/show.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
+#: kallithea/templates/admin/users/user_edit_api_keys.html:8
+#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/users/user_edit_api_keys.html:32
 msgid "Expires"
 msgstr ""
 
@@ -2512,7 +2648,7 @@
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:27
 #: kallithea/templates/admin/users/user_edit_api_keys.html:8
 #: kallithea/templates/admin/users/user_edit_api_keys.html:27
-msgid "never"
+msgid "Never"
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:145
@@ -2520,7 +2656,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:89
+#: kallithea/templates/changeset/changeset_file_comment.html:79
 msgid "Cancel"
 msgstr ""
 
@@ -2574,21 +2710,23 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/new.html:58
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:70
 #: kallithea/templates/admin/my_account/my_account_emails.html:46
 #: kallithea/templates/admin/my_account/my_account_password.html:34
 #: kallithea/templates/admin/my_account/my_account_profile.html:71
-#: kallithea/templates/admin/permissions/permissions_globals.html:109
+#: kallithea/templates/admin/permissions/permissions_globals.html:113
 #: kallithea/templates/admin/permissions/permissions_ips.html:39
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
 #: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
 #: kallithea/templates/admin/repos/repo_edit_fields.html:59
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:135
+#: kallithea/templates/admin/repos/repo_edit_settings.html:137
 #: kallithea/templates/admin/settings/settings_global.html:57
 #: kallithea/templates/admin/settings/settings_vcs.html:81
 #: kallithea/templates/admin/settings/settings_visual.html:117
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
+#: kallithea/templates/admin/users/user_edit_api_keys.html:15
 #: kallithea/templates/admin/users/user_edit_api_keys.html:70
 #: kallithea/templates/admin/users/user_edit_emails.html:46
 #: kallithea/templates/admin/users/user_edit_ips.html:50
@@ -2622,8 +2760,18 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/my_account/my_account_emails.html:19
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/changeset/changeset_file_comment.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:18
+#: kallithea/templates/admin/settings/settings_hooks.html:36
+#: kallithea/templates/admin/users/user_edit_emails.html:19
+#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/changeset/changeset_file_comment.html:30
+#: kallithea/templates/data_table/_dt_elements.html:129
+#: kallithea/templates/data_table/_dt_elements.html:157
+#: kallithea/templates/data_table/_dt_elements.html:173
+#: kallithea/templates/data_table/_dt_elements.html:189
 #: kallithea/templates/files/files_source.html:39
 #: kallithea/templates/files/files_source.html:42
 #: kallithea/templates/files/files_source.html:45
@@ -2635,10 +2783,20 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/changeset/changeset_file_comment.html:91
-#: kallithea/templates/changeset/changeset_file_comment.html:207
+#: kallithea/templates/admin/repos/repo_edit_settings.html:27
+#: kallithea/templates/base/perms_summary.html:43
+#: kallithea/templates/base/perms_summary.html:79
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/changeset/changeset_file_comment.html:191
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/data_table/_dt_elements.html:123
+#: kallithea/templates/data_table/_dt_elements.html:150
+#: kallithea/templates/data_table/_dt_elements.html:151
 #: kallithea/templates/data_table/_dt_elements.html:165
+#: kallithea/templates/data_table/_dt_elements.html:167
 #: kallithea/templates/data_table/_dt_elements.html:181
+#: kallithea/templates/data_table/_dt_elements.html:183
 #: kallithea/templates/files/diff_2way.html:56
 #: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/files/files_source.html:44
@@ -2672,26 +2830,29 @@
 msgid "Profile"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_edit.html:30
-msgid "API Keys"
+#: kallithea/templates/admin/my_account/my_account.html:36
+msgid "Email Addresses"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:38
-msgid "My Emails"
+#: kallithea/templates/admin/users/user_edit.html:31
+msgid "API Keys"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:39
-msgid "My Repositories"
+msgid "Owned Repositories"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:40
 #: kallithea/templates/journal/journal.html:53
-msgid "Watched"
+msgid "Watched Repositories"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:41
-msgid "My Permissions"
+#: kallithea/templates/admin/permissions/permissions.html:30
+#: kallithea/templates/admin/user_groups/user_group_edit.html:32
+#: kallithea/templates/admin/users/user_edit.html:34
+msgid "Show Permissions"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:6
@@ -2699,56 +2860,42 @@
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
-msgid "expires"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #, python-format
-msgid "Confirm to reset this api key: %s"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-msgid "reset"
+msgid "Confirm to reset this API key: %s"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:30
 #: kallithea/templates/admin/users/user_edit_api_keys.html:30
-msgid "expired"
+msgid "Expired"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:40
 #: kallithea/templates/admin/users/user_edit_api_keys.html:40
 #, python-format
-msgid "Confirm to remove this api key: %s"
+msgid "Confirm to remove this API key: %s"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:42
 #: kallithea/templates/admin/users/user_edit_api_keys.html:42
-msgid "remove"
+msgid "Remove"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:49
 #: kallithea/templates/admin/users/user_edit_api_keys.html:49
-msgid "No additional api keys specified"
+msgid "No additional API keys specified"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:61
 #: kallithea/templates/admin/users/user_edit_api_keys.html:61
-msgid "New api key"
+msgid "New API key"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:69
 #: kallithea/templates/admin/my_account/my_account_emails.html:45
 #: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:85
+#: kallithea/templates/admin/repos/repo_add_base.html:81
 #: kallithea/templates/admin/repos/repo_edit_fields.html:58
 #: kallithea/templates/admin/users/user_edit_api_keys.html:69
 #: kallithea/templates/admin/users/user_edit_emails.html:45
@@ -2761,19 +2908,6 @@
 msgid "Primary"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-msgid "delete"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_emails.html:20
 #: kallithea/templates/admin/users/user_edit_emails.html:20
 #, python-format
@@ -2827,7 +2961,7 @@
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:16
 #: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "current IP"
+msgid "Current IP"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:28
@@ -2890,15 +3024,9 @@
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions.html:5
-msgid "Permissions Administration"
-msgstr ""
-
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
-#: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/base/base.html:64
-msgid "Permissions"
+msgid "Default Permissions"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions.html:28
@@ -2907,14 +3035,10 @@
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions.html:29
-#: kallithea/templates/admin/users/user_edit.html:34
+#: kallithea/templates/admin/users/user_edit.html:32
 msgid "IP Whitelist"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions.html:30
-msgid "Overview"
-msgstr ""
-
 #: kallithea/templates/admin/permissions/permissions_globals.html:7
 msgid "Anonymous access"
 msgstr ""
@@ -2924,19 +3048,21 @@
 msgid "Allow access to Kallithea without needing to log in. Anonymous users use %s user permissions."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:25
+msgid "All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:26
-msgid "All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost"
+msgid "Apply to all existing repositories"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:27
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
-msgid "Overwrite existing settings"
+msgid "Permissions for the Default user on new repositories."
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repo_add_base.html:37
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
 #: kallithea/templates/data_table/_dt_elements.html:202
 #: kallithea/templates/forks/fork.html:48
 msgid "Repository group"
@@ -2946,47 +3072,79 @@
 msgid "All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+msgid "Apply to all existing repository groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:41
+msgid "Permissions for the Default user on new repository groups."
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:46
 #: kallithea/templates/data_table/_dt_elements.html:209
 msgid "User group"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:53
-msgid "All default permissions on each user group will be reset to chosen permission, note that all custom default permission on repository groups will be lost"
+msgid "All default permissions on each user group will be reset to chosen permission, note that all custom default permission on user groups will be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:54
+msgid "Apply to all existing user groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:55
+msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:60
-msgid "Repository creation"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:68
+msgid "Top level repository creation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
+msgid "Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:65
+msgid "Note: This will also give all users API access to create repositories everywhere. That might change in future versions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:70
 msgid "Repository creation with group write access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:72
-msgid "Write permission to a repository group allows creating repositories inside that group."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:77
+#: kallithea/templates/admin/permissions/permissions_globals.html:74
+msgid "With this, write permission to a repository group allows creating repositories inside that group. Without this, group write permissions mean nothing."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:79
 msgid "User group creation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:85
+#: kallithea/templates/admin/permissions/permissions_globals.html:83
+msgid "Enable this to allow non-admins to create user groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
 msgid "Repository forking"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:93
+#: kallithea/templates/admin/permissions/permissions_globals.html:92
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:97
 msgid "Registration"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:101
+#: kallithea/templates/admin/permissions/permissions_globals.html:105
 msgid "External auth account activation"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:13
 #: kallithea/templates/admin/users/user_edit_ips.html:23
 #, python-format
-msgid "Confirm to delete this ip: %s"
+msgid "Confirm to delete this IP address: %s"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:19
@@ -2996,7 +3154,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:30
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
@@ -3020,12 +3178,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repos/repo_add_base.html:46
 msgid "Copy parent group permissions"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:54
+#: kallithea/templates/admin/repos/repo_add_base.html:50
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
@@ -3052,10 +3210,16 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
 #: kallithea/templates/admin/repos/repo_edit.html:46
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
-#: kallithea/templates/admin/users/user_edit.html:31
+#: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr ""
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
+#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/user_groups/user_group_edit.html:31
+msgid "Permissions"
+msgstr ""
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:1
 #, python-format
 msgid "Repository Group: %s"
@@ -3076,7 +3240,7 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:147
+#: kallithea/templates/pullrequests/pullrequest_show.html:146
 msgid "Created on"
 msgstr ""
 
@@ -3092,38 +3256,10 @@
 msgid "Delete this repository group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
-#: kallithea/templates/base/perms_summary.html:14
-msgid "none"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
-#: kallithea/templates/base/perms_summary.html:15
-msgid "read"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
-#: kallithea/templates/base/perms_summary.html:16
-msgid "write"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
-#: kallithea/templates/base/perms_summary.html:17
-msgid "admin"
-msgstr ""
-
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:12
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
-msgid "user/user group"
+msgid "User/User Group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
@@ -3132,7 +3268,7 @@
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:37
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
-msgid "default"
+msgid "Default"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
@@ -3141,12 +3277,12 @@
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:68
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
-msgid "revoke"
+msgid "Revoke"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-msgid "delegated admin"
+msgid "Delegated Admin"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
@@ -3156,7 +3292,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
-msgid "apply to children"
+msgid "Apply to children"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
@@ -3200,42 +3336,37 @@
 msgid "Number of Top-level Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:14
-msgid "Import existing repository ?"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:23
-#: kallithea/templates/summary/summary.html:29
-msgid "Clone from"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:27
-msgid "Optional URL from which repository should be cloned."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:36
-#: kallithea/templates/admin/repos/repo_edit_settings.html:76
+#: kallithea/templates/admin/repos/repo_add_base.html:17
+msgid "Clone remote repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:22
+msgid "Optional: URL of a remote repository. If set, the repository will be created as a clone from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:32
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 #: kallithea/templates/forks/fork.html:42
 msgid "Keep it short and to the point. Use a README file for longer descriptions."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:45
-#: kallithea/templates/admin/repos/repo_edit_settings.html:46
+#: kallithea/templates/admin/repos/repo_add_base.html:41
+#: kallithea/templates/admin/repos/repo_edit_settings.html:48
 #: kallithea/templates/forks/fork.html:52
 msgid "Optionally select a group to put this repository into."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:63
+#: kallithea/templates/admin/repos/repo_add_base.html:59
 msgid "Type of repository to create."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_settings.html:53
+#: kallithea/templates/forks/fork.html:58
+msgid "Landing revision"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_add_base.html:68
-#: kallithea/templates/admin/repos/repo_edit_settings.html:51
-#: kallithea/templates/forks/fork.html:58
-msgid "Landing revision"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:72
 msgid "Default revision for files page, downloads, full text search index and readme generation"
 msgstr ""
 
@@ -3276,8 +3407,8 @@
 
 #: kallithea/templates/admin/repos/repo_edit.html:58
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:175
-#: kallithea/templates/summary/summary.html:176
+#: kallithea/templates/summary/summary.html:171
+#: kallithea/templates/summary/summary.html:172
 msgid "Statistics"
 msgstr ""
 
@@ -3437,23 +3568,23 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:21
-msgid "private repository"
+msgid "Private Repository"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_remote.html:3
-msgid "Remote URL"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_remote.html:8
-msgid "Pull Changes from Remote Location"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_remote.html:8
-msgid "Confirm to pull changes from remote side."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_remote.html:14
-msgid "This repository does not have a remote URL set."
+msgid "Remote repository URL"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+msgid "Pull Changes from Remote Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:11
+msgid "Confirm to pull changes from remote repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "This repository does not have a remote repository URL."
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_settings.html:11
@@ -3476,36 +3607,22 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_settings.html:21
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:27
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
-#: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:183
-msgid "edit"
+msgid "Remote repository"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_settings.html:30
-msgid "new value"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:37
-msgid "URL used for doing remote pulls."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:55
+msgid "New URL"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
+msgid "Optional: URL of a remote repository. If set, the repository can be pulled from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
 msgid "Change owner of this repository."
 msgstr ""
 
@@ -3725,7 +3842,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:4
-msgid "check for updates"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:5
@@ -3912,6 +4029,7 @@
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
+#: kallithea/templates/admin/user_groups/user_groups.html:10
 #: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
 msgid "User Groups"
 msgstr ""
@@ -3931,15 +4049,8 @@
 msgid "%s user group settings"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit.html:31
-msgid "Default permissions"
-msgstr ""
-
 #: kallithea/templates/admin/user_groups/user_group_edit.html:33
-#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
-msgid "Members"
+msgid "Show Members"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:1
@@ -3947,6 +4058,12 @@
 msgid "User Group: %s"
 msgstr ""
 
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
+#: kallithea/templates/admin/user_groups/user_groups.html:48
+msgid "Members"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
 #: kallithea/templates/data_table/_dt_elements.html:174
 #, python-format
@@ -3973,10 +4090,6 @@
 msgid "User Groups Administration"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-msgid "user groups"
-msgstr ""
-
 #: kallithea/templates/admin/users/user_add.html:5
 msgid "Add user"
 msgstr ""
@@ -4002,11 +4115,7 @@
 msgid "%s user settings"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit.html:32
-msgid "Default Permissions"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit.html:33
+#: kallithea/templates/admin/users/user_edit.html:30
 msgid "Emails"
 msgstr ""
 
@@ -4184,12 +4293,6 @@
 msgid "Show recent activity"
 msgstr ""
 
-#: kallithea/templates/base/base.html:229
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
-msgid "Journal"
-msgstr ""
-
 #: kallithea/templates/base/base.html:234
 #: kallithea/templates/base/base.html:235
 msgid "Public journal"
@@ -4255,12 +4358,12 @@
 msgstr ""
 
 #: kallithea/templates/base/default_perms_box.html:14
-msgid "Inherit from defaults"
+msgid "Inherit defaults"
 msgstr ""
 
 #: kallithea/templates/base/default_perms_box.html:19
 #, python-format
-msgid "Select to inherit permissions from %s permissions settings, and default IP address whitelist."
+msgid "Select to inherit global settings, IP whitelist and permissions from the %s."
 msgstr ""
 
 #: kallithea/templates/base/default_perms_box.html:28
@@ -4288,7 +4391,8 @@
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:13
-msgid "show"
+#: kallithea/templates/changelog/changelog.html:42
+msgid "Show"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:22
@@ -4384,6 +4488,14 @@
 msgid "Confirm to revoke permission for {0}: {1} ?"
 msgstr ""
 
+#: kallithea/templates/base/root.html:40
+msgid "enabled"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "disabled"
+msgstr ""
+
 #: kallithea/templates/base/root.html:43
 msgid "Specify changeset"
 msgstr ""
@@ -4413,6 +4525,7 @@
 #: kallithea/templates/branches/branches.html:54
 #: kallithea/templates/branches/branches_data.html:12
 #: kallithea/templates/changelog/changelog_summary_data.html:7
+#: kallithea/templates/files/files_browser.html:32
 #: kallithea/templates/pullrequests/pullrequest.html:62
 #: kallithea/templates/pullrequests/pullrequest.html:78
 #: kallithea/templates/tags/tags.html:54
@@ -4441,10 +4554,6 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changelog/changelog.html:42
-msgid "Show"
-msgstr ""
-
 #: kallithea/templates/changelog/changelog.html:52
 msgid "Clear selection"
 msgstr ""
@@ -4474,7 +4583,7 @@
 #, python-format
 msgid ""
 "Changeset status: %s\n"
-"Click to open associated pull request #%s"
+"Click to open associated pull request %s"
 msgstr ""
 
 #: kallithea/templates/changelog/changelog.html:96
@@ -4484,7 +4593,7 @@
 msgstr ""
 
 #: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:48
+#: kallithea/templates/compare/compare_cs.html:63
 msgid "Expand commit message"
 msgstr ""
 
@@ -4517,7 +4626,7 @@
 msgid "Branch %s"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:290
+#: kallithea/templates/changelog/changelog.html:291
 msgid "There are no changes yet"
 msgstr ""
 
@@ -4587,15 +4696,15 @@
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:36
-msgid "parent rev."
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:42
-msgid "child rev."
+msgid "Child rev."
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:43
+#: kallithea/templates/changeset/changeset_file_comment.html:37
 #: kallithea/templates/changeset/changeset_range.html:48
 msgid "Changeset status"
 msgstr ""
@@ -4618,7 +4727,7 @@
 
 #: kallithea/templates/changeset/changeset.html:89
 #: kallithea/templates/changeset/changeset_range.html:88
-msgid "merge"
+msgid "Merge"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:123
@@ -4631,7 +4740,7 @@
 
 #: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:307
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
@@ -4640,7 +4749,7 @@
 
 #: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:311
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
@@ -4649,124 +4758,119 @@
 
 #: kallithea/templates/changeset/changeset.html:153
 #: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/pullrequests/pullrequest_show.html:328
-#: kallithea/templates/pullrequests/pullrequest_show.html:351
+#: kallithea/templates/pullrequests/pullrequest_show.html:330
+#: kallithea/templates/pullrequests/pullrequest_show.html:354
 msgid "Show full diff anyway"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:224
-#: kallithea/templates/changeset/changeset.html:261
-msgid "no revisions"
+#: kallithea/templates/changeset/changeset.html:225
+#: kallithea/templates/changeset/changeset.html:262
+msgid "No revisions"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:21
+msgid "on pull request"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:22
+msgid "No title"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset_file_comment.html:24
-msgid "Status change from pull request"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:25
-#: kallithea/templates/changeset/changeset_file_comment.html:28
-msgid "No title"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:27
-msgid "Comment from pull request"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:32
-msgid "Status change on changeset"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:34
-msgid "Comment on changeset"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:50
+msgid "on this changeset"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:30
 msgid "Delete comment?"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:67
+#: kallithea/templates/changeset/changeset_file_comment.html:37
+msgid "Status change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:57
 msgid "Commenting on line {1}."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:68
-#: kallithea/templates/changeset/changeset_file_comment.html:163
+#: kallithea/templates/changeset/changeset_file_comment.html:58
+#: kallithea/templates/changeset/changeset_file_comment.html:147
 #, python-format
 msgid "Comments parsed using %s syntax with %s support."
 msgstr ""
 
+#: kallithea/templates/changeset/changeset_file_comment.html:60
+msgid "Use @username inside this text to notify another user"
+msgstr ""
+
 #: kallithea/templates/changeset/changeset_file_comment.html:70
-msgid "Use @username inside this text to notify another user"
+#: kallithea/templates/changeset/changeset_file_comment.html:183
+msgid "Comment preview"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:75
+msgid "Submitting ..."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:78
+#: kallithea/templates/changeset/changeset_file_comment.html:189
+msgid "Comment"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:199
-msgid "Comment preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:85
-msgid "Submitting ..."
+#: kallithea/templates/changeset/changeset_file_comment.html:190
+msgid "Preview"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset_file_comment.html:88
-#: kallithea/templates/changeset/changeset_file_comment.html:205
-msgid "Comment"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
-#: kallithea/templates/changeset/changeset_file_comment.html:206
-msgid "Preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:98
 msgid "You need to be logged in to comment."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:98
+#: kallithea/templates/changeset/changeset_file_comment.html:88
 msgid "Login now"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:102
+#: kallithea/templates/changeset/changeset_file_comment.html:92
 msgid "Hide"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:114
+#: kallithea/templates/changeset/changeset_file_comment.html:104
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:115
+#: kallithea/templates/changeset/changeset_file_comment.html:105
 #, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:116
+#: kallithea/templates/changeset/changeset_file_comment.html:106
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:165
+#: kallithea/templates/changeset/changeset_file_comment.html:149
 msgid "Use @username inside this text to notify another user."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:172
+#: kallithea/templates/changeset/changeset_file_comment.html:156
 msgid "Vote for pull request status"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:174
+#: kallithea/templates/changeset/changeset_file_comment.html:158
 msgid "Set changeset status"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:178
+#: kallithea/templates/changeset/changeset_file_comment.html:162
 msgid "No change"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:191
+#: kallithea/templates/changeset/changeset_file_comment.html:175
 msgid "Close"
 msgstr ""
 
@@ -4810,29 +4914,41 @@
 msgid "Ancestor"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:61
+#: kallithea/templates/compare/compare_cs.html:44
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:46
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:48
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:76
 msgid "Show merge diff"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:71
-#: kallithea/templates/pullrequests/pullrequest_show.html:299
+#: kallithea/templates/compare/compare_cs.html:86
+#: kallithea/templates/pullrequests/pullrequest_show.html:301
 msgid "Common ancestor"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:75
+#: kallithea/templates/compare/compare_cs.html:90
 msgid "No common ancestor found - repositories are unrelated"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:83
+#: kallithea/templates/compare/compare_cs.html:98
 msgid "is"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:84
+#: kallithea/templates/compare/compare_cs.html:99
 #, python-format
 msgid "%s changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:85
+#: kallithea/templates/compare/compare_cs.html:100
 msgid "behind"
 msgstr ""
 
@@ -4856,7 +4972,7 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:294
+#: kallithea/templates/pullrequests/pullrequest_show.html:296
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
@@ -4864,7 +4980,7 @@
 msgstr[1] ""
 
 #: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:315
+#: kallithea/templates/pullrequests/pullrequest_show.html:317
 msgid "No files"
 msgstr ""
 
@@ -5022,10 +5138,6 @@
 msgid "Commit Changes"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:32
-msgid "revision"
-msgstr ""
-
 #: kallithea/templates/files/files_browser.html:33
 msgid "Previous revision"
 msgstr ""
@@ -5051,18 +5163,14 @@
 msgstr ""
 
 #: kallithea/templates/files/files_browser.html:62
-msgid "Mimetype"
+msgid "Last Revision"
 msgstr ""
 
 #: kallithea/templates/files/files_browser.html:63
-msgid "Last Revision"
+msgid "Last Modified"
 msgstr ""
 
 #: kallithea/templates/files/files_browser.html:64
-msgid "Last Modified"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:65
 msgid "Last Committer"
 msgstr ""
 
@@ -5174,8 +5282,8 @@
 msgstr ""
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:146
-#: kallithea/templates/summary/summary.html:147
+#: kallithea/templates/summary/summary.html:142
+#: kallithea/templates/summary/summary.html:143
 msgid "Followers"
 msgstr ""
 
@@ -5226,8 +5334,8 @@
 msgstr ""
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:152
-#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:148
+#: kallithea/templates/summary/summary.html:149
 msgid "Forks"
 msgstr ""
 
@@ -5248,18 +5356,13 @@
 msgstr ""
 
 #: kallithea/templates/journal/journal.html:56
-msgid "My Repos"
+msgid "My Repositories"
 msgstr ""
 
 #: kallithea/templates/journal/journal_data.html:43
 msgid "No entries yet"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
-msgid "Public Journal"
-msgstr ""
-
 #: kallithea/templates/journal/public_journal.html:13
 msgid "ATOM public journal feed"
 msgstr ""
@@ -5302,12 +5405,12 @@
 msgstr ""
 
 #: kallithea/templates/pullrequests/pullrequest.html:97
-#: kallithea/templates/pullrequests/pullrequest_show.html:210
+#: kallithea/templates/pullrequests/pullrequest_show.html:209
 msgid "Pull Request Reviewers"
 msgstr ""
 
 #: kallithea/templates/pullrequests/pullrequest.html:107
-#: kallithea/templates/pullrequests/pullrequest_show.html:239
+#: kallithea/templates/pullrequests/pullrequest_show.html:241
 msgid "Type name of reviewer to add"
 msgstr ""
 
@@ -5361,12 +5464,12 @@
 
 #: kallithea/templates/pullrequests/pullrequest_show.html:6
 #, python-format
-msgid "%s Pull Request #%s"
+msgid "%s Pull Request %s"
 msgstr ""
 
 #: kallithea/templates/pullrequests/pullrequest_show.html:10
 #, python-format
-msgid "Pull request #%s from %s#%s"
+msgid "Pull request %s from %s#%s"
 msgstr ""
 
 #: kallithea/templates/pullrequests/pullrequest_show.html:57
@@ -5382,86 +5485,82 @@
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:94
+#: kallithea/templates/pullrequests/pullrequest_show.html:93
 msgid "Still not reviewed by"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:98
+#: kallithea/templates/pullrequests/pullrequest_show.html:97
 #, python-format
 msgid "%d reviewer"
 msgid_plural "%d reviewers"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:100
+#: kallithea/templates/pullrequests/pullrequest_show.html:99
 msgid "Pull request was reviewed by all reviewers"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:102
+#: kallithea/templates/pullrequests/pullrequest_show.html:101
 msgid "There are no reviewers"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:108
+#: kallithea/templates/pullrequests/pullrequest_show.html:107
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:114
+#: kallithea/templates/pullrequests/pullrequest_show.html:113
 msgid "on"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:121
+#: kallithea/templates/pullrequests/pullrequest_show.html:120
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:132
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
 msgid "Pull changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:155
+#: kallithea/templates/pullrequests/pullrequest_show.html:154
 msgid "Created by"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:170
+#: kallithea/templates/pullrequests/pullrequest_show.html:169
 msgid "Update"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:188
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:224
-msgid "owner"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:224
-msgid "reviewer"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:227
+#: kallithea/templates/pullrequests/pullrequest_show.html:226
+msgid "Reviewer"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:229
 msgid "Remove reviewer"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:247
+#: kallithea/templates/pullrequests/pullrequest_show.html:249
 msgid "Potential Reviewers"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:252
 msgid "Click to add the repository owner as reviewer:"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:273
-msgid "Save Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:274
-msgid "Save as New Pull Request"
-msgstr ""
-
 #: kallithea/templates/pullrequests/pullrequest_show.html:275
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:276
+msgid "Save as New Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:277
 msgid "Cancel Changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
+#: kallithea/templates/pullrequests/pullrequest_show.html:287
 msgid "Pull Request Content"
 msgstr ""
 
@@ -5562,8 +5661,8 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:104
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:100
+#: kallithea/templates/summary/summary.html:116
 msgid "Enable"
 msgstr ""
 
@@ -5572,12 +5671,12 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:353
+#: kallithea/templates/summary/summary.html:349
 msgid "files"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:377
+#: kallithea/templates/summary/summary.html:373
 msgid "Show more"
 msgstr ""
 
@@ -5631,6 +5730,14 @@
 msgid "Fork of"
 msgstr ""
 
+#: kallithea/templates/summary/summary.html:29
+msgid "Clone from"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:72
+msgid "Clone URL"
+msgstr ""
+
 #: kallithea/templates/summary/summary.html:78
 msgid "Show by Name"
 msgstr ""
@@ -5639,57 +5746,57 @@
 msgid "Show by ID"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:96
+#: kallithea/templates/summary/summary.html:92
 msgid "Trending files"
 msgstr ""
 
+#: kallithea/templates/summary/summary.html:108
+msgid "Download"
+msgstr ""
+
 #: kallithea/templates/summary/summary.html:112
-msgid "Download"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:116
 msgid "There are no downloads yet"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:118
+#: kallithea/templates/summary/summary.html:114
 msgid "Downloads are disabled for this repository"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:124
+#: kallithea/templates/summary/summary.html:120
 msgid "Download as zip"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:129
+#: kallithea/templates/summary/summary.html:125
 msgid "Check this to download archive with subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:129
-msgid "with subrepos"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:160
+#: kallithea/templates/summary/summary.html:125
+msgid "With subrepos"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:156
 msgid "Repository Size"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:167
-#: kallithea/templates/summary/summary.html:169
+#: kallithea/templates/summary/summary.html:163
+#: kallithea/templates/summary/summary.html:165
 msgid "Feed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:190
+#: kallithea/templates/summary/summary.html:186
 msgid "Latest Changes"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:192
+#: kallithea/templates/summary/summary.html:188
 msgid "Quick Start"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:206
+#: kallithea/templates/summary/summary.html:202
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:297
+#: kallithea/templates/summary/summary.html:293
 #, python-format
 msgid "Download %s as %s"
 msgstr ""
--- a/kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -1000,7 +1000,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:488
@@ -1008,7 +1008,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr ""
 
 #: kallithea/lib/auth.py:745
@@ -2001,7 +2001,7 @@
 msgstr ""
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
 #: kallithea/model/validators.py:818
@@ -3062,7 +3062,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/pl/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/pl/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -1014,7 +1014,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr "Dodano ip %s do listy dozwolonych adresów użytkownia"
 
 #: kallithea/controllers/admin/users.py:488
@@ -1022,7 +1022,7 @@
 msgstr "Wystąpił błąd podczas zapisywania e-maila"
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr "Usunięto adres ip z listy dozwolonych adresów dla użytkownika"
 
 #: kallithea/lib/auth.py:745
@@ -2046,7 +2046,7 @@
 msgstr "Rewizja  %(revs)s jest już częścią  nowej gałęzi więc określ jego status"
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "Proszę podać poprawny adres IPv4 lub IPv6"
 
 #: kallithea/model/validators.py:818
@@ -3138,7 +3138,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr "Nowy adres ip"
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -1010,7 +1010,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:488
@@ -1018,7 +1018,7 @@
 msgstr "Ocorreu um erro durante o salvamento do IP"
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr ""
 
 #: kallithea/lib/auth.py:745
@@ -2038,7 +2038,7 @@
 "estado"
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "Por favor, forneça um endereço válido IPv4 ou IPv6"
 
 #: kallithea/model/validators.py:818
@@ -3126,7 +3126,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr "Novo endereço IP"
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/ru/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/ru/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -1019,7 +1019,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr "Добавлен IP %s в белый список пользователя"
 
 #: kallithea/controllers/admin/users.py:488
@@ -1027,7 +1027,7 @@
 msgstr "Произошла ошибка при сохранении IP"
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr "Удален IP %s из белого списка пользователя"
 
 #: kallithea/lib/auth.py:745
@@ -2054,8 +2054,8 @@
 "статус"
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
-msgstr "Пожалуйста, введите существующий IPv4 или IpV6 адре"
+msgid "Please enter a valid IPv4 or IPv6 address"
+msgstr "Пожалуйста, введите существующий IPv4 или IPv6 адре"
 
 #: kallithea/model/validators.py:818
 #, python-format
@@ -3130,7 +3130,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr "Новый IP-адрес"
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/sk/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/sk/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -997,7 +997,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:488
@@ -1005,7 +1005,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr ""
 
 #: kallithea/lib/auth.py:745
@@ -2004,7 +2004,7 @@
 msgstr ""
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
 #: kallithea/model/validators.py:818
@@ -3067,7 +3067,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -1006,7 +1006,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:488
@@ -1014,7 +1014,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr ""
 
 #: kallithea/lib/auth.py:745
@@ -2004,7 +2004,7 @@
 msgstr "修订%(revs)s已经包含在拉取请求中或者或者已经设置状态"
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
 #: kallithea/model/validators.py:818
@@ -3077,7 +3077,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po	Mon Jul 20 15:07:54 2015 +0200
@@ -998,7 +998,7 @@
 
 #: kallithea/controllers/admin/users.py:482
 #, python-format
-msgid "Added ip %s to user whitelist"
+msgid "Added IP address %s to user whitelist"
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:488
@@ -1006,7 +1006,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/users.py:502
-msgid "Removed ip address from user whitelist"
+msgid "Removed IP address from user whitelist"
 msgstr ""
 
 #: kallithea/lib/auth.py:745
@@ -1995,7 +1995,7 @@
 msgstr ""
 
 #: kallithea/model/validators.py:817
-msgid "Please enter a valid IPv4 or IpV6 address"
+msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
 #: kallithea/model/validators.py:818
@@ -3065,7 +3065,7 @@
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
-msgid "New ip address"
+msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
--- a/kallithea/lib/auth.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/auth.py	Mon Jul 20 15:07:54 2015 +0200
@@ -144,21 +144,6 @@
     return KallitheaCrypto.hash_check(password, hashed)
 
 
-class CookieStoreWrapper(object):
-
-    def __init__(self, cookie_store):
-        self.cookie_store = cookie_store
-
-    def __repr__(self):
-        return 'CookieStore<%s>' % (self.cookie_store)
-
-    def get(self, key, other=None):
-        if isinstance(self.cookie_store, dict):
-            return self.cookie_store.get(key, other)
-        elif isinstance(self.cookie_store, AuthUser):
-            return self.cookie_store.__dict__.get(key, other)
-
-
 
 def _cached_perms_data(user_id, user_is_admin, user_inherit_default_permissions,
                        explicit, algo):
@@ -474,34 +459,45 @@
 
 class AuthUser(object):
     """
-    A simple object that handles all attributes of user in Kallithea
+    Represents a Kallithea user, including various authentication and
+    authorization information. Typically used to store the current user,
+    but is also used as a generic user information data structure in
+    parts of the code, e.g. user management.
 
-    It does lookup based on API key,given user, or user present in session
-    Then it fills all required information for such user. It also checks if
-    anonymous access is enabled and if so, it returns default user as logged in
+    Constructed from user ID, username, API key or cookie dict, it looks
+    up the matching database `User` and copies all attributes to itself,
+    adding various non-persistent data. If lookup fails but anonymous
+    access to Kallithea is enabled, the default user is loaded instead.
+
+    `AuthUser` does not by itself authenticate users and the constructor
+    sets the `is_authenticated` field to False, except when falling back
+    to the default anonymous user (if enabled). It's up to other parts
+    of the code to check e.g. if a supplied password is correct, and if
+    so, set `is_authenticated` to True.
     """
 
-    def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
+    def __init__(self, user_id=None, api_key=None, username=None,
+            is_external_auth=False):
 
         self.user_id = user_id
         self._api_key = api_key
 
         self.api_key = None
         self.username = username
-        self.ip_addr = ip_addr
         self.name = ''
         self.lastname = ''
         self.email = ''
         self.is_authenticated = False
         self.admin = False
         self.inherit_default_permissions = False
+        self.is_external_auth = is_external_auth
 
         self.propagate_data()
         self._instance = None
 
     @LazyProperty
     def permissions(self):
-        return self.get_perms(user=self, cache=False)
+        return self.__get_perms(user=self, cache=False)
 
     @property
     def api_keys(self):
@@ -517,9 +513,9 @@
             log.debug('Auth User lookup by USER ID %s' % self.user_id)
             is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
 
-        # try go get user by api key
+        # try go get user by API key
         elif self._api_key and self._api_key != self.anonymous_user.api_key:
-            log.debug('Auth User lookup by API KEY %s' % self._api_key)
+            log.debug('Auth User lookup by API key %s' % self._api_key)
             is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
 
         # lookup by username
@@ -545,13 +541,13 @@
 
         log.debug('Auth User is now %s' % self)
 
-    def get_perms(self, user, explicit=True, algo='higherwin', cache=False):
+    def __get_perms(self, user, explicit=True, algo='higherwin', cache=False):
         """
         Fills user permission attribute with permissions taken from database
         works for permissions given for repositories, and for permissions that
         are granted to groups
 
-        :param user: instance of User object from database
+        :param user: `AuthUser` instance
         :param explicit: In case there are permissions both for user and a group
             that user is part of, explicit flag will define if user will
             explicitly override permissions from group, if it's False it will
@@ -609,23 +605,14 @@
         return [x[0] for x in self.permissions['user_groups'].iteritems()
                 if x[1] == 'usergroup.admin']
 
-    @property
-    def ip_allowed(self):
-        """
-        Checks if ip_addr used in constructor is allowed from defined list of
-        allowed ip_addresses for user
-
-        :returns: boolean, True if ip is in allowed ip range
+    @staticmethod
+    def check_ip_allowed(user, ip_addr):
         """
-        # check IP
-        inherit = self.inherit_default_permissions
-        return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
-                                         inherit_from_default=inherit)
-
-    @classmethod
-    def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
-        allowed_ips = AuthUser.get_allowed_ips(user_id, cache=True,
-                        inherit_from_default=inherit_from_default)
+        Check if the given IP address (a `str`) is allowed for the given
+        user (an `AuthUser` or `db.User`).
+        """
+        allowed_ips = AuthUser.get_allowed_ips(user.user_id, cache=True,
+            inherit_from_default=user.inherit_default_permissions)
         if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
             log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
             return True
@@ -635,30 +622,37 @@
             return False
 
     def __repr__(self):
-        return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
-            % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
+        return "<AuthUser('id:%s[%s] auth:%s')>"\
+            % (self.user_id, self.username, self.is_authenticated)
 
     def set_authenticated(self, authenticated=True):
         if self.user_id != self.anonymous_user.user_id:
             self.is_authenticated = authenticated
 
-    def get_cookie_store(self):
-        return {'username': self.username,
-                'user_id': self.user_id,
-                'is_authenticated': self.is_authenticated}
+    def to_cookie(self):
+        """ Serializes this login session to a cookie `dict`. """
+        return {
+            'user_id': self.user_id,
+            'username': self.username,
+            'is_authenticated': self.is_authenticated,
+            'is_external_auth': self.is_external_auth,
+        }
 
-    @classmethod
-    def from_cookie_store(cls, cookie_store):
+    @staticmethod
+    def from_cookie(cookie):
         """
-        Creates AuthUser from a cookie store
-
-        :param cls:
-        :param cookie_store:
+        Deserializes an `AuthUser` from a cookie `dict`.
         """
-        user_id = cookie_store.get('user_id')
-        username = cookie_store.get('username')
-        api_key = cookie_store.get('api_key')
-        return AuthUser(user_id, api_key, username)
+
+        au = AuthUser(
+            user_id=cookie.get('user_id'),
+            username=cookie.get('username'),
+            is_external_auth=cookie.get('is_external_auth', False),
+        )
+        if not au.is_authenticated and au.user_id is not None:
+            # user is not authenticated and not empty
+            au.set_authenticated(cookie.get('is_authenticated'))
+        return au
 
     @classmethod
     def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
@@ -717,6 +711,15 @@
 #==============================================================================
 # CHECK DECORATORS
 #==============================================================================
+
+def redirect_to_login(message=None):
+    from kallithea.lib import helpers as h
+    p = url.current()
+    if message:
+        h.flash(h.literal(message), category='warning')
+    log.debug('Redirecting to login page, origin: %s' % p)
+    return redirect(url('login_home', came_from=p, **request.GET))
+
 class LoginRequired(object):
     """
     Must be logged in to execute this function else
@@ -733,61 +736,45 @@
         return decorator(self.__wrapper, func)
 
     def __wrapper(self, func, *fargs, **fkwargs):
-        cls = fargs[0]
-        user = cls.authuser
-        loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
+        controller = fargs[0]
+        user = controller.authuser
+        loc = "%s:%s" % (controller.__class__.__name__, func.__name__)
+        log.debug('Checking access for user %s @ %s' % (user, loc))
 
-        # check if our IP is allowed
-        ip_access_valid = True
-        if not user.ip_allowed:
-            from kallithea.lib import helpers as h
-            h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
-                    category='warning')
-            ip_access_valid = False
+        if not AuthUser.check_ip_allowed(user, controller.ip_addr):
+            return redirect_to_login(_('IP %s not allowed') % controller.ip_addr)
 
-        # check if we used an APIKEY and it's a valid one
-        # defined whitelist of controllers which API access will be enabled
-        _api_key = request.GET.get('api_key', '')
-        api_access_valid = allowed_api_access(loc, api_key=_api_key)
-
-        # explicit controller is enabled or API is in our whitelist
-        if self.api_access or api_access_valid:
-            log.debug('Checking API KEY access for %s' % cls)
-            if _api_key and _api_key in user.api_keys:
-                api_access_valid = True
-                log.debug('API KEY ****%s is VALID' % _api_key[-4:])
+        # check if we used an API key and it's a valid one
+        api_key = request.GET.get('api_key')
+        if api_key is not None:
+            # explicit controller is enabled or API is in our whitelist
+            if self.api_access or allowed_api_access(loc, api_key=api_key):
+                if api_key in user.api_keys:
+                    log.info('user %s authenticated with API key ****%s @ %s'
+                             % (user, api_key[-4:], loc))
+                    return func(*fargs, **fkwargs)
+                else:
+                    log.warning('API key ****%s is NOT valid' % api_key[-4:])
+                    return redirect_to_login(_('Invalid API key'))
             else:
-                api_access_valid = False
-                if not _api_key:
-                    log.debug("API KEY *NOT* present in request")
-                else:
-                    log.warning("API KEY ****%s *NOT* valid" % _api_key[-4:])
+                # controller does not allow API access
+                log.warning('API access to %s is not allowed' % loc)
+                return abort(403)
 
         # CSRF protection - POSTs with session auth must contain correct token
-        if request.POST and user.is_authenticated and not api_access_valid:
+        if request.POST and user.is_authenticated:
             token = request.POST.get(secure_form.token_key)
             if not token or token != secure_form.authentication_token():
                 log.error('CSRF check failed')
                 return abort(403)
 
-        log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
-        reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
-
-        if ip_access_valid and (user.is_authenticated or api_access_valid):
-            log.info('user %s authenticating with:%s IS authenticated on func %s '
-                     % (user, reason, loc)
-            )
+        # regular user authentication
+        if user.is_authenticated:
+            log.info('user %s authenticated with regular auth @ %s' % (user, loc))
             return func(*fargs, **fkwargs)
         else:
-            log.warning('user %s authenticating with:%s NOT authenticated on func: %s: '
-                     'IP_ACCESS:%s API_ACCESS:%s'
-                     % (user, reason, loc, ip_access_valid, api_access_valid)
-            )
-            p = url.current()
-
-            log.debug('redirecting to login page with %s' % p)
-            return redirect(url('login_home', came_from=p))
-
+            log.warning('user %s NOT authenticated with regular auth @ %s' % (user, loc))
+            return redirect_to_login()
 
 class NotAnonymous(object):
     """
@@ -806,13 +793,8 @@
         anonymous = self.user.username == User.DEFAULT_USER
 
         if anonymous:
-            p = url.current()
-
-            import kallithea.lib.helpers as h
-            h.flash(_('You need to be a registered user to '
-                      'perform this action'),
-                    category='warning')
-            return redirect(url('login_home', came_from=p))
+            return redirect_to_login(_('You need to be a registered user to '
+                    'perform this action'))
         else:
             return func(*fargs, **fkwargs)
 
@@ -843,14 +825,7 @@
             anonymous = self.user.username == User.DEFAULT_USER
 
             if anonymous:
-                p = url.current()
-
-                import kallithea.lib.helpers as h
-                h.flash(_('You need to be signed in to '
-                          'view this page'),
-                        category='warning')
-                return redirect(url('login_home', came_from=p))
-
+                return redirect_to_login(_('You need to be signed in to view this page'))
             else:
                 # redirect with forbidden ret code
                 return abort(403)
--- a/kallithea/lib/base.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/base.py	Mon Jul 20 15:07:54 2015 +0200
@@ -28,6 +28,7 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
+import datetime
 import logging
 import time
 import traceback
@@ -48,7 +49,7 @@
 from kallithea.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
     safe_str, safe_int
 from kallithea.lib import auth_modules
-from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware, CookieStoreWrapper
+from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware
 from kallithea.lib.utils import get_repo_slug
 from kallithea.lib.exceptions import UserCreationError
 from kallithea.lib.vcs.exceptions import RepositoryError, EmptyRepositoryError, ChangesetDoesNotExistError
@@ -103,6 +104,41 @@
     return path
 
 
+def log_in_user(user, remember, is_external_auth):
+    """
+    Log a `User` in and update session and cookies. If `remember` is True,
+    the session cookie is set to expire in a year; otherwise, it expires at
+    the end of the browser session.
+
+    Returns populated `AuthUser` object.
+    """
+    user.update_lastlogin()
+    meta.Session().commit()
+
+    auth_user = AuthUser(user_id=user.user_id,
+                         is_external_auth=is_external_auth)
+    auth_user.set_authenticated()
+
+    # Start new session to prevent session fixation attacks.
+    session.invalidate()
+    session['authuser'] = cookie = auth_user.to_cookie()
+
+    # If they want to be remembered, update the cookie
+    if remember:
+        t = datetime.datetime.now() + datetime.timedelta(days=365)
+        session._set_cookie_expires(t)
+
+    session.save()
+
+    log.info('user %s is now authenticated and stored in '
+             'session, session attrs %s', user.username, cookie)
+
+    # dumps session attrs back to cookie
+    session._update_cookie_out()
+
+    return auth_user
+
+
 class BasicAuth(paste.auth.basic.AuthBasicAuthenticator):
 
     def __init__(self, realm, authfunc, auth_http_code=None):
@@ -182,13 +218,11 @@
         name
 
         :param action: push or pull action
-        :param user: user instance
+        :param user: `User` instance
         :param repo_name: repository name
         """
         # check IP
-        inherit = user.inherit_default_permissions
-        ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
-                                               inherit_from_default=inherit)
+        ip_allowed = AuthUser.check_ip_allowed(user, ip_addr)
         if ip_allowed:
             log.info('Access for IP:%s allowed' % (ip_addr,))
         else:
@@ -341,44 +375,72 @@
         self.sa = meta.Session
         self.scm_model = ScmModel(self.sa)
 
+    @staticmethod
+    def _determine_auth_user(api_key, session_authuser):
+        """
+        Create an `AuthUser` object given the IP address of the request, the
+        API key (if any), and the authuser from the session.
+        """
+
+        # Authenticate by API key
+        if api_key:
+            # when using API_KEY we are sure user exists.
+            return AuthUser(api_key=api_key, is_external_auth=True)
+
+        # Authenticate by session cookie
+        cookie = session.get('authuser')
+        # In ancient login sessions, 'authuser' may not be a dict.
+        # In that case, the user will have to log in again.
+        if isinstance(cookie, dict):
+            try:
+                return AuthUser.from_cookie(cookie)
+            except UserCreationError as e:
+                # container auth or other auth functions that create users on
+                # the fly can throw UserCreationError to signal issues with
+                # user creation. Explanation should be provided in the
+                # exception object.
+                from kallithea.lib import helpers as h
+                h.flash(e, 'error', logf=log.error)
+
+        # Authenticate by auth_container plugin (if enabled)
+        if any(
+            auth_modules.importplugin(name).is_container_auth
+            for name in Setting.get_auth_plugins()
+        ):
+            try:
+                auth_info = auth_modules.authenticate('', '', request.environ)
+            except UserCreationError as e:
+                from kallithea.lib import helpers as h
+                h.flash(e, 'error', logf=log.error)
+            else:
+                if auth_info:
+                    username = auth_info['username']
+                    user = User.get_by_username(username, case_insensitive=True)
+                    return log_in_user(user, remember=False,
+                                       is_external_auth=True)
+
+        # User is anonymous
+        return AuthUser()
+
     def __call__(self, environ, start_response):
         """Invoke the Controller"""
+
         # WSGIController.__call__ dispatches to the Controller method
         # the request is routed to. This routing information is
         # available in environ['pylons.routes_dict']
         try:
             self.ip_addr = _get_ip_addr(environ)
             # make sure that we update permissions each time we call controller
-            api_key = request.GET.get('api_key')
 
-            if api_key:
-                # when using API_KEY we are sure user exists.
-                auth_user = AuthUser(api_key=api_key, ip_addr=self.ip_addr)
-                authenticated = False
-            else:
-                cookie_store = CookieStoreWrapper(session.get('authuser'))
-                try:
-                    auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
-                                         ip_addr=self.ip_addr)
-                except UserCreationError, e:
-                    from kallithea.lib import helpers as h
-                    h.flash(e, 'error')
-                    # container auth or other auth functions that create users on
-                    # the fly can throw this exception signaling that there's issue
-                    # with user creation, explanation should be provided in
-                    # Exception itself
-                    auth_user = AuthUser(ip_addr=self.ip_addr)
+            #set globals for auth user
+            self.authuser = c.authuser = request.user = self._determine_auth_user(
+                request.GET.get('api_key'),
+                session.get('authuser'),
+            )
 
-                authenticated = cookie_store.get('is_authenticated')
-
-            if not auth_user.is_authenticated and auth_user.user_id is not None:
-                # user is not authenticated and not empty
-                auth_user.set_authenticated(authenticated)
-            request.user = auth_user
-            #set globals for auth user
-            self.authuser = c.authuser = auth_user
-            log.info('IP: %s User: %s accessed %s' % (
-               self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
+            log.info('IP: %s User: %s accessed %s',
+                self.ip_addr, self.authuser,
+                safe_unicode(_get_access_path(environ)),
             )
             return WSGIController.__call__(self, environ, start_response)
         finally:
--- a/kallithea/lib/dbmigrate/schema/db_2_2_0.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/dbmigrate/schema/db_2_2_0.py	Mon Jul 20 15:07:54 2015 +0200
@@ -437,7 +437,7 @@
     user_comments = relationship('ChangesetComment', cascade='all')
     #extra emails for this user
     user_emails = relationship('UserEmailMap', cascade='all')
-    #extra api keys
+    #extra API keys
     user_api_keys = relationship('UserApiKeys', cascade='all')
 
 
@@ -1239,7 +1239,7 @@
                 pass
 
         return get_clone_url(uri_tmpl=uri_tmpl,
-                             qualifed_home_url=qualified_home_url,
+                             qualified_home_url=qualified_home_url,
                              repo_name=self.repo_name,
                              repo_id=self.repo_id, **override)
 
--- a/kallithea/lib/dbmigrate/schema/db_2_2_3.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/dbmigrate/schema/db_2_2_3.py	Mon Jul 20 15:07:54 2015 +0200
@@ -437,7 +437,7 @@
     user_comments = relationship('ChangesetComment', cascade='all')
     #extra emails for this user
     user_emails = relationship('UserEmailMap', cascade='all')
-    #extra api keys
+    #extra API keys
     user_api_keys = relationship('UserApiKeys', cascade='all')
 
 
@@ -1263,7 +1263,7 @@
                 pass
 
         return get_clone_url(uri_tmpl=uri_tmpl,
-                             qualifed_home_url=qualified_home_url,
+                             qualified_home_url=qualified_home_url,
                              repo_name=self.repo_name,
                              repo_id=self.repo_id, **override)
 
--- a/kallithea/lib/diffs.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/diffs.py	Mon Jul 20 15:07:54 2015 +0200
@@ -193,8 +193,8 @@
         (?:^\+\+\+[ ](b/(?P<b_file>.+?)|/dev/null)\t?(?:\n|$))?
     """, re.VERBOSE | re.MULTILINE)
 
-    #used for inline highlighter word split
-    _token_re = re.compile(r'()(&gt;|&lt;|&amp;|<u>\t</u>| <i></i>|\W+?)')
+    # Used for inline highlighter word split, must match the substitutions in _escaper
+    _token_re = re.compile(r'()(&amp;|&lt;|&gt;|<u>\t</u>|<u class="cr"></u>| <i></i>|\W+?)')
 
     _escape_re = re.compile(r'(&)|(<)|(>)|(\t)|(\r)|(?<=.)( \n| $)')
 
--- a/kallithea/lib/exceptions.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/exceptions.py	Mon Jul 20 15:07:54 2015 +0200
@@ -45,6 +45,7 @@
 
 
 class DefaultUserException(Exception):
+    """An invalid action was attempted on the default user"""
     pass
 
 
@@ -98,6 +99,10 @@
     pass
 
 
+class UserInvalidException(Exception):
+    pass
+
+
 class RepositoryCreationError(Exception):
     pass
 
--- a/kallithea/lib/helpers.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/helpers.py	Mon Jul 20 15:07:54 2015 +0200
@@ -89,18 +89,17 @@
         parts = url('home', qualified=True).split('://', 1)
         return parts[1].split('/', 1)[0]
 
-def html_escape(text, html_escape_table=None):
-    """Produce entities within text."""
-    if not html_escape_table:
-        html_escape_table = {
-            "&": "&amp;",
-            '"': "&quot;",
-            "'": "&apos;",
-            ">": "&gt;",
-            "<": "&lt;",
-        }
-    return "".join(html_escape_table.get(c, c) for c in text)
-
+def html_escape(text):
+    """Return string with all html escaped.
+    This is also safe for javascript in html but not necessarily correct.
+    """
+    return (text
+        .replace('&', '&amp;')
+        .replace(">", "&gt;")
+        .replace("<", "&lt;")
+        .replace('"', "&quot;")
+        .replace("'", "&apos;")
+        )
 
 def shorter(text, size=20):
     postfix = '...'
@@ -406,6 +405,25 @@
 
 class Flash(_Flash):
 
+    def __call__(self, message, category=None, ignore_duplicate=False, logf=None):
+        """
+        Show a message to the user _and_ log it through the specified function
+
+        category: notice (default), warning, error, success
+        logf: a custom log function - such as log.debug
+
+        logf defaults to log.info, unless category equals 'success', in which
+        case logf defaults to log.debug.
+        """
+        if logf is None:
+            logf = log.info
+            if category == 'success':
+                logf = log.debug
+
+        logf('Flash %s: %s', category, message)
+
+        super(Flash, self).__call__(message, category, ignore_duplicate)
+
     def pop_messages(self):
         """Return all accumulated messages and delete them from the session.
 
@@ -423,7 +441,7 @@
 #==============================================================================
 from kallithea.lib.vcs.utils import author_name, author_email
 from kallithea.lib.utils2 import credentials_filter, age as _age
-from kallithea.model.db import User, ChangesetStatus
+from kallithea.model.db import User, ChangesetStatus, PullRequest
 
 age = lambda  x, y=False: _age(x, y)
 capitalize = lambda x: x.capitalize()
@@ -545,13 +563,13 @@
     if not value:
         return ''
 
-    value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
+    value = re.sub(r'\[see\ \=&gt;\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
                    '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
-    value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
+    value = re.sub(r'\[license\ \=&gt;\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
                    '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
-    value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
+    value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=&gt;\ *([a-zA-Z0-9\-\/]*)\]',
                    '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
-    value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
+    value = re.sub(r'\[(lang|language)\ \=&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
                    '<div class="metatag" tag="lang">\\2</div>', value)
     value = re.sub(r'\[([a-z]+)\]',
                   '<div class="metatag" tag="\\1">\\1</div>', value)
@@ -679,7 +697,7 @@
                 url('changeset_home', repo_name=repo_name,
                     revision=_rev
                 ),
-                _('compare view')
+                _('Compare view')
             )
         )
 
@@ -724,7 +742,7 @@
     def get_fork_name():
         repo_name = action_params
         _url = url('summary_home', repo_name=repo_name)
-        return _('fork name %s') % link_to(action_params, _url)
+        return _('Fork name %s') % link_to(action_params, _url)
 
     def get_user_name():
         user_name = action_params
@@ -736,12 +754,15 @@
 
     def get_pull_request():
         pull_request_id = action_params
+        nice_id = PullRequest.make_nice_id(pull_request_id)
+
         deleted = user_log.repository is None
         if deleted:
             repo_name = user_log.repository_name
         else:
             repo_name = user_log.repository.repo_name
-        return link_to(_('Pull request #%s') % pull_request_id,
+
+        return link_to(_('Pull request %s') % nice_id,
                     url('pullrequest_show', repo_name=repo_name,
                     pull_request_id=pull_request_id))
 
@@ -1243,21 +1264,26 @@
     return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
 
 
-def urlify_text(text_, safe=True):
+def _urlify_text(s):
     """
     Extract urls from text and make html links out of them
-
-    :param text_:
     """
-
     def url_func(match_obj):
-        url_full = match_obj.groups()[0]
+        url_full = match_obj.group(1)
         return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
-    _newtext = url_re.sub(url_func, text_)
-    if safe:
-        return literal(_newtext)
-    return _newtext
+    return url_re.sub(url_func, s)
 
+def urlify_text(s, truncate=None, stylize=False, truncatef=truncate):
+    """
+    Extract urls from text and make literal html links out of them
+    """
+    if truncate is not None:
+        s = truncatef(s, truncate)
+    s = html_escape(s)
+    if stylize:
+        s = desc_stylize(s)
+    s = _urlify_text(s)
+    return literal(s)
 
 def urlify_changesets(text_, repository):
     """
@@ -1298,14 +1324,13 @@
     :param repository:
     :param link_: changeset link
     """
-    def escaper(string):
-        return string.replace('<', '&lt;').replace('>', '&gt;')
+    newtext = html_escape(text_)
 
     # urlify changesets - extract revisions and make link out of them
-    newtext = urlify_changesets(escaper(text_), repository)
+    newtext = urlify_changesets(newtext, repository)
 
     # extract http/https links and make them real urls
-    newtext = urlify_text(newtext, safe=False)
+    newtext = _urlify_text(newtext)
 
     newtext = urlify_issues(newtext, repository, link_)
 
--- a/kallithea/lib/hooks.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/hooks.py	Mon Jul 20 15:07:54 2015 +0200
@@ -447,21 +447,21 @@
                         repo._repo.refs.set_symbolic_ref('HEAD',
                                             'refs/heads/%s' % push_ref['name'])
 
-                    cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
+                    cmd = ['for-each-ref', '--format=%(refname)','refs/heads/*']
                     heads = repo.run_git_command(cmd)[0]
+                    cmd = ['log', push_ref['new_rev'],
+                           '--reverse', '--pretty=format:%H', '--not']
                     heads = heads.replace(push_ref['ref'], '')
-                    heads = ' '.join(map(lambda c: c.strip('\n').strip(),
-                                         heads.splitlines()))
-                    cmd = (('log %(new_rev)s' % push_ref) +
-                           ' --reverse --pretty=format:"%H" --not ' + heads)
+                    for l in heads.splitlines():
+                        cmd.append(l.strip())
                     git_revs += repo.run_git_command(cmd)[0].splitlines()
 
                 elif push_ref['new_rev'] == EmptyChangeset().raw_id:
                     #delete branch case
                     git_revs += ['delete_branch=>%s' % push_ref['name']]
                 else:
-                    cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
-                           ' --reverse --pretty=format:"%H"')
+                    cmd = ['log', '%(old_rev)s..%(new_rev)s' % push_ref,
+                           '--reverse', '--pretty=format:%H']
                     git_revs += repo.run_git_command(cmd)[0].splitlines()
 
             elif _type == 'tags':
--- a/kallithea/lib/middleware/pygrack.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/middleware/pygrack.py	Mon Jul 20 15:07:54 2015 +0200
@@ -82,13 +82,12 @@
         server_advert = '# service=%s' % git_command
         packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
         _git_path = kallithea.CONFIG.get('git_path', 'git')
+        cmd = [_git_path, git_command[4:],
+               '--stateless-rpc', '--advertise-refs', self.content_path]
+        log.debug('handling cmd %s', cmd)
         try:
-            out = subprocessio.SubprocessIOChunker(
-                r'%s %s --stateless-rpc --advertise-refs "%s"' % (
-                    _git_path, git_command[4:], self.content_path),
-                starting_values=[
-                    packet_len + server_advert + '0000'
-                ]
+            out = subprocessio.SubprocessIOChunker(cmd,
+                starting_values=[packet_len + server_advert + '0000']
             )
         except EnvironmentError, e:
             log.error(traceback.format_exc())
@@ -118,21 +117,17 @@
         else:
             inputstream = environ['wsgi.input']
 
+        gitenv = dict(os.environ)
+        # forget all configs
+        gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
+        cmd = [_git_path, git_command[4:], '--stateless-rpc', self.content_path]
+        log.debug('handling cmd %s', cmd)
         try:
-            gitenv = os.environ
-            # forget all configs
-            gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
-            opts = dict(
-                env=gitenv,
-                cwd=self.content_path,
-            )
-            cmd = r'%s %s --stateless-rpc "%s"' % (_git_path, git_command[4:],
-                                                   self.content_path),
-            log.debug('handling cmd %s' % cmd)
             out = subprocessio.SubprocessIOChunker(
                 cmd,
                 inputstream=inputstream,
-                **opts
+                env=gitenv,
+                cwd=self.content_path,
             )
         except EnvironmentError, e:
             log.error(traceback.format_exc())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/lib/middleware/sessionmiddleware.py	Mon Jul 20 15:07:54 2015 +0200
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.lib.middleware.sessionmiddleware
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+session management middleware
+
+This file overrides Beaker's built-in SessionMiddleware
+class to automagically use secure cookies over HTTPS.
+
+Original Beaker SessionMiddleware class written by Ben Bangert
+"""
+
+from beaker.session import SessionObject
+from beaker.middleware import SessionMiddleware
+
+class SecureSessionMiddleware(SessionMiddleware):
+    def __call__(self, environ, start_response):
+        """
+        This function's implementation is taken directly from Beaker,
+        with HTTPS detection added. When accessed over HTTPS, force
+        setting cookie's secure flag.
+
+        The only difference from that original code is that we switch
+        the secure option on and off depending on the URL scheme (first
+        two lines). To avoid concurrency issues, we use a local options
+        variable.
+        """
+        options = dict(self.options)
+        options["secure"] = environ['wsgi.url_scheme'] == 'https'
+
+        session = SessionObject(environ, **options)
+        if environ.get('paste.registry'):
+            if environ['paste.registry'].reglist:
+                environ['paste.registry'].register(self.session, session)
+        environ[self.environ_key] = session
+        environ['beaker.get_session'] = self._get_session
+
+        if 'paste.testing_variables' in environ and 'webtest_varname' in options:
+            environ['paste.testing_variables'][options['webtest_varname']] = session
+
+        def session_start_response(status, headers, exc_info=None):
+            if session.accessed():
+                session.persist()
+                if session.__dict__['_headers']['set_cookie']:
+                    cookie = session.__dict__['_headers']['cookie_out']
+                    if cookie:
+                        headers.append(('Set-cookie', cookie))
+            return start_response(status, headers, exc_info)
+        return self.wrap_app(environ, session_start_response)
--- a/kallithea/lib/paster_commands/make_rcextensions.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/paster_commands/make_rcextensions.py	Mon Jul 20 15:07:54 2015 +0200
@@ -30,7 +30,6 @@
 
 import os
 import sys
-import logging
 import pkg_resources
 
 from kallithea.lib.utils import BasePasterCommand, ask_ok
@@ -40,46 +39,44 @@
 rc_path = dn(dn(dn(os.path.realpath(__file__))))
 sys.path.append(rc_path)
 
-log = logging.getLogger(__name__)
-
 
 class Command(BasePasterCommand):
 
     max_args = 1
     min_args = 1
 
-    usage = "CONFIG_FILE"
     group_name = "Kallithea"
     takes_config_file = -1
     parser = BasePasterCommand.standard_parser(verbose=True)
-    summary = "Creates additional extensions for kallithea"
+    summary = "Write template file for extending Kallithea in Python."
+    usage = "CONFIG_FILE"
+    description = '''\
+        A rcextensions directory with a __init__.py file will be created next to
+        the ini file. Local customizations in that file will survive upgrades.
+        The file contains instructions on how it can be customized.
+        '''
 
     def command(self):
-        logging.config.fileConfig(self.path_to_ini_file)
         from pylons import config
 
-        def _make_file(ext_file, tmpl):
-            bdir = os.path.split(ext_file)[0]
-            if not os.path.isdir(bdir):
-                os.makedirs(bdir)
-            with open(ext_file, 'wb') as f:
-                f.write(tmpl)
-                log.info('Writen new extensions file to %s' % ext_file)
-
         here = config['here']
-        tmpl = pkg_resources.resource_string(
+        content = pkg_resources.resource_string(
             'kallithea', os.path.join('config', 'rcextensions', '__init__.py')
         )
         ext_file = os.path.join(here, 'rcextensions', '__init__.py')
         if os.path.exists(ext_file):
             msg = ('Extension file already exists, do you want '
                    'to overwrite it ? [y/n]')
-            if ask_ok(msg):
-                _make_file(ext_file, tmpl)
-            else:
-                log.info('nothing done...')
-        else:
-            _make_file(ext_file, tmpl)
+            if not ask_ok(msg):
+                print 'Nothing done...'
+                return
+
+        dirname = os.path.dirname(ext_file)
+        if not os.path.isdir(dirname):
+            os.makedirs(dirname)
+        with open(ext_file, 'wb') as f:
+            f.write(content)
+            print 'Wrote new extensions file to %s' % ext_file
 
     def update_parser(self):
         pass
--- a/kallithea/lib/recaptcha.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/recaptcha.py	Mon Jul 20 15:07:54 2015 +0200
@@ -55,7 +55,7 @@
     recaptcha_challenge_field -- The value of recaptcha_challenge_field from the form
     recaptcha_response_field -- The value of recaptcha_response_field from the form
     private_key -- your reCAPTCHA private key
-    remoteip -- the user's ip address
+    remoteip -- the user's IP address
     """
 
     if not (recaptcha_response_field and recaptcha_challenge_field and
--- a/kallithea/lib/utils.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/utils.py	Mon Jul 20 15:07:54 2015 +0200
@@ -161,7 +161,7 @@
         easy translations
     :param repo: string name of repository or object containing repo_id,
         that action was made on
-    :param ipaddr: optional ip address from what the action was made
+    :param ipaddr: optional IP address from what the action was made
     :param sa: optional sqlalchemy session
 
     """
@@ -799,7 +799,7 @@
     if 'git' not in BACKENDS:
         return None
 
-    stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
+    stdout, stderr = GitRepository._run_git_command(['--version'], _bare=True,
                                                     _safe=True)
 
     m = re.search("\d+.\d+.\d+", stdout)
--- a/kallithea/lib/utils2.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/utils2.py	Mon Jul 20 15:07:54 2015 +0200
@@ -459,7 +459,7 @@
 
     proto = ''
 
-    for pat in ('https://', 'http://'):
+    for pat in ('https://', 'http://', 'git://'):
         if uri.startswith(pat):
             uri = uri[len(pat):]
             proto = pat
@@ -493,8 +493,8 @@
     return ''.join(uri)
 
 
-def get_clone_url(uri_tmpl, qualifed_home_url, repo_name, repo_id, **override):
-    parsed_url = urlobject.URLObject(qualifed_home_url)
+def get_clone_url(uri_tmpl, qualified_home_url, repo_name, repo_id, **override):
+    parsed_url = urlobject.URLObject(qualified_home_url)
     decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
     args = {
         'scheme': parsed_url.scheme,
@@ -615,25 +615,25 @@
 
 def _extract_extras(env=None):
     """
-    Extracts the rc extras data from os.environ, and wraps it into named
+    Extracts the Kallithea extras data from os.environ, and wraps it into named
     AttributeDict object
     """
     if not env:
         env = os.environ
 
     try:
-        rc_extras = json.loads(env['KALLITHEA_EXTRAS'])
+        extras = json.loads(env['KALLITHEA_EXTRAS'])
     except KeyError:
-        rc_extras = {}
+        extras = {}
 
     try:
         for k in ['username', 'repository', 'locked_by', 'scm', 'make_lock',
                   'action', 'ip']:
-            rc_extras[k]
+            extras[k]
     except KeyError, e:
-        raise Exception('Missing key %s in os.environ %s' % (e, rc_extras))
+        raise Exception('Missing key %s in os.environ %s' % (e, extras))
 
-    return AttributeDict(rc_extras)
+    return AttributeDict(extras)
 
 
 def _set_extras(extras):
--- a/kallithea/lib/vcs/backends/git/changeset.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/vcs/backends/git/changeset.py	Mon Jul 20 15:07:54 2015 +0200
@@ -189,7 +189,7 @@
         """
         rev_filter = settings.GIT_REV_FILTER
         so, se = self.repository.run_git_command(
-            "rev-list %s --children" % (rev_filter)
+            ['rev-list', rev_filter, '--children']
         )
 
         children = []
@@ -288,12 +288,12 @@
         f_path = safe_str(path)
 
         if limit:
-            cmd = 'log -n %s --pretty="format: %%H" -s %s -- "%s"' % (
-                      safe_int(limit, 0), cs_id, f_path)
+            cmd = ['log', '-n', str(safe_int(limit, 0)),
+                   '--pretty=format:%H', '-s', cs_id, '--', f_path]
 
         else:
-            cmd = 'log --pretty="format: %%H" -s %s -- "%s"' % (
-                      cs_id, f_path)
+            cmd = ['log',
+                   '--pretty=format:%H', '-s', cs_id, '--', f_path]
         so, se = self.repository.run_git_command(cmd)
         ids = re.findall(r'[0-9a-fA-F]{40}', so)
         return [self.repository.get_changeset(sha) for sha in ids]
@@ -321,7 +321,7 @@
         generally not good. Should be replaced with algorithm iterating
         commits.
         """
-        cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
+        cmd = ['blame', '-l', '--root', '-r', self.id, '--', path]
         # -l     ==> outputs long shas (and we need all 40 characters)
         # --root ==> doesn't put '^' character for boundaries
         # -r sha ==> blames for the given revision
@@ -471,8 +471,8 @@
     def _diff_name_status(self):
         output = []
         for parent in self.parents:
-            cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id,
-                                                                self.raw_id)
+            cmd = ['diff', '--name-status', parent.raw_id, self.raw_id,
+                   '--encoding=utf8']
             so, se = self.repository.run_git_command(cmd)
             output.append(so.strip())
         return '\n'.join(output)
--- a/kallithea/lib/vcs/backends/git/repository.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/vcs/backends/git/repository.py	Mon Jul 20 15:07:54 2015 +0200
@@ -12,23 +12,12 @@
 import os
 import re
 import time
+import errno
 import urllib
 import urllib2
 import logging
 import posixpath
 import string
-import sys
-if sys.platform == "win32":
-    from subprocess import list2cmdline
-    def quote(s):
-        return list2cmdline([s])
-else:
-    try:
-        # Python <=2.7
-        from pipes import quote
-    except ImportError:
-        # Python 3.3+
-        from shlex import quote
 
 from dulwich.objects import Tag
 from dulwich.repo import Repo, NotGitRepository
@@ -134,10 +123,7 @@
             del opts['_safe']
             safe_call = True
 
-        _str_cmd = False
-        if isinstance(cmd, basestring):
-            cmd = [cmd]
-            _str_cmd = True
+        assert isinstance(cmd, list), cmd
 
         gitenv = os.environ
         # need to clean fix GIT_DIR !
@@ -147,13 +133,11 @@
 
         _git_path = settings.GIT_EXECUTABLE_PATH
         cmd = [_git_path] + _copts + cmd
-        if _str_cmd:
-            cmd = ' '.join(cmd)
 
         try:
             _opts = dict(
                 env=gitenv,
-                shell=True,
+                shell=False,
             )
             _opts.update(opts)
             p = subprocessio.SubprocessIOChunker(cmd, **_opts)
@@ -190,6 +174,9 @@
         if os.path.isdir(url) or url.startswith('file:'):
             return True
 
+        if url.startswith('git://'):
+            return True
+
         if '+' in url[:url.find('://')]:
             url = url[url.find('+') + 1:]
 
@@ -267,7 +254,7 @@
             return []
 
         rev_filter = settings.GIT_REV_FILTER
-        cmd = 'rev-list %s --reverse --date-order' % (rev_filter)
+        cmd = ['rev-list', rev_filter, '--reverse', '--date-order']
         try:
             so, se = self.run_git_command(cmd)
         except RepositoryError:
@@ -550,22 +537,16 @@
 
         # %H at format means (full) commit hash, initial hashes are retrieved
         # in ascending date order
-        cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
-        cmd_params = {}
+        cmd = ['log', '--date-order', '--reverse', '--pretty=format:%H']
         if start_date:
-            cmd_template += ' --since "$since"'
-            cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
+            cmd += ['--since', start_date.strftime('%m/%d/%y %H:%M:%S')]
         if end_date:
-            cmd_template += ' --until "$until"'
-            cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
+            cmd += ['--until', end_date.strftime('%m/%d/%y %H:%M:%S')]
         if branch_name:
-            cmd_template += ' $branch_name'
-            cmd_params['branch_name'] = branch_name
+            cmd.append(branch_name)
         else:
-            rev_filter = settings.GIT_REV_FILTER
-            cmd_template += ' %s' % (rev_filter)
+            cmd.append(settings.GIT_REV_FILTER)
 
-        cmd = string.Template(cmd_template).safe_substitute(**cmd_params)
         revs = self.run_git_command(cmd)[0].splitlines()
         start_pos = 0
         end_pos = len(revs)
@@ -621,14 +602,14 @@
 
         if rev1 == self.EMPTY_CHANGESET:
             rev2 = self.get_changeset(rev2).raw_id
-            cmd = ' '.join(['show'] + flags + [rev2])
+            cmd = ['show'] + flags + [rev2]
         else:
             rev1 = self.get_changeset(rev1).raw_id
             rev2 = self.get_changeset(rev2).raw_id
-            cmd = ' '.join(['diff'] + flags + [rev1, rev2])
+            cmd = ['diff'] + flags + [rev1, rev2]
 
         if path:
-            cmd += ' -- "%s"' % path
+            cmd += ['--', path]
 
         stdout, stderr = self.run_git_command(cmd)
         # TODO: don't ignore stderr
@@ -662,8 +643,7 @@
             cmd.append('--bare')
         elif not update_after_clone:
             cmd.append('--no-checkout')
-        cmd += ['--', quote(url), quote(self.path)]
-        cmd = ' '.join(cmd)
+        cmd += ['--', url, self.path]
         # If error occurs run_git_command raises RepositoryError already
         self.run_git_command(cmd)
 
@@ -672,8 +652,7 @@
         Tries to pull changes from external location.
         """
         url = self._get_url(url)
-        cmd = ['pull', "--ff-only", quote(url)]
-        cmd = ' '.join(cmd)
+        cmd = ['pull', '--ff-only', url]
         # If error occurs run_git_command raises RepositoryError already
         self.run_git_command(cmd)
 
@@ -682,13 +661,11 @@
         Tries to pull changes from external location.
         """
         url = self._get_url(url)
-        so, se = self.run_git_command('ls-remote -h %s' % quote(url))
-        refs = []
+        so, se = self.run_git_command(['ls-remote', '-h', url])
+        cmd = ['fetch', url, '--']
         for line in (x for x in so.splitlines()):
             sha, ref = line.split('\t')
-            refs.append(ref)
-        refs = ' '.join(('+%s:%s' % (r, r) for r in refs))
-        cmd = '''fetch %s -- %s''' % (quote(url), refs)
+            cmd.append('+%s:%s' % (ref, ref))
         self.run_git_command(cmd)
 
     def _update_server_info(self):
@@ -696,7 +673,13 @@
         runs gits update-server-info command in this repo instance
         """
         from dulwich.server import update_server_info
-        update_server_info(self._repo)
+        try:
+            update_server_info(self._repo)
+        except OSError, e:
+            if e.errno != errno.ENOENT:
+                raise
+            # Workaround for dulwich crashing on for example its own dulwich/tests/data/repos/simple_merge.git/info/refs.lock
+            log.error('Ignoring error running update-server-info: %s', e)
 
     @LazyProperty
     def workdir(self):
--- a/kallithea/lib/vcs/backends/hg/repository.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/vcs/backends/hg/repository.py	Mon Jul 20 15:07:54 2015 +0200
@@ -229,7 +229,7 @@
 
     def _get_all_revisions(self):
 
-        return map(lambda x: hex(x[7]), self._repo.changelog.index)[:-1]
+        return [self._repo[x].hex() for x in self._repo.filtered('visible').changelog.revs()]
 
     def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
                   context=3):
@@ -264,6 +264,7 @@
 
         return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
                           opts=diffopts(git=True,
+                                        showfunc=True,
                                         ignorews=ignore_whitespace,
                                         context=context)))
 
--- a/kallithea/lib/vcs/subprocessio.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/vcs/subprocessio.py	Mon Jul 20 15:07:54 2015 +0200
@@ -342,11 +342,9 @@
             input_streamer.start()
             inputstream = input_streamer.output
 
-        _shell = kwargs.get('shell', True)
-        if isinstance(cmd, (list, tuple)):
-            cmd = ' '.join(cmd)
+        # Note: fragile cmd mangling has been removed for use in Kallithea
+        assert isinstance(cmd, list), cmd
 
-        kwargs['shell'] = _shell
         _p = subprocess.Popen(cmd, bufsize=-1,
                               stdin=inputstream,
                               stdout=subprocess.PIPE,
--- a/kallithea/lib/vcs/utils/lazy.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/lib/vcs/utils/lazy.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,3 +1,6 @@
+import threading
+
+
 class _Missing(object):
 
     def __repr__(self):
@@ -41,8 +44,6 @@
             obj.__dict__[self.__name__] = value
         return value
 
-import threading
-
 
 class ThreadLocalLazyProperty(LazyProperty):
     """
--- a/kallithea/model/api_key.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/model/api_key.py	Mon Jul 20 15:07:54 2015 +0200
@@ -15,7 +15,7 @@
 kallithea.model.api_key
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-api key model for Kallithea
+API key model for Kallithea
 
 This file was forked by the Kallithea project in July 2014.
 Original author and date, and relevant copyright and licensing information is below:
--- a/kallithea/model/changeset_status.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/model/changeset_status.py	Mon Jul 20 15:07:54 2015 +0200
@@ -66,9 +66,27 @@
         q = q.order_by(ChangesetStatus.version.asc())
         return q
 
+    def _calculate_status(self, statuses):
+        """
+        Given a list of statuses, calculate the resulting status, according to
+        the policy: approve if consensus, reject when at least one reject.
+        """
+
+        if not statuses:
+            return ChangesetStatus.STATUS_UNDER_REVIEW
+
+        if all(st and st.status == ChangesetStatus.STATUS_APPROVED for st in statuses):
+            return ChangesetStatus.STATUS_APPROVED
+
+        if any(st and st.status == ChangesetStatus.STATUS_REJECTED for st in statuses):
+            return ChangesetStatus.STATUS_REJECTED
+
+        return ChangesetStatus.STATUS_UNDER_REVIEW
+
     def calculate_pull_request_result(self, pull_request):
         """
-        Policy: approve if consensus. Only approve and reject counts as valid votes.
+        Return a tuple (reviewers, pending reviewers, pull request status)
+        Only approve and reject counts as valid votes.
         """
 
         # collect latest votes from all voters
@@ -77,24 +95,21 @@
                                              pull_request=pull_request,
                                              with_revisions=True)):
             cs_statuses[st.author.username] = st
+
         # collect votes from official reviewers
         pull_request_reviewers = []
         pull_request_pending_reviewers = []
-        approved_votes = 0
+        relevant_statuses = []
         for r in pull_request.reviewers:
             st = cs_statuses.get(r.user.username)
-            if st and st.status == ChangesetStatus.STATUS_APPROVED:
-                approved_votes += 1
+            relevant_statuses.append(st)
             if not st or st.status in (ChangesetStatus.STATUS_NOT_REVIEWED,
                                        ChangesetStatus.STATUS_UNDER_REVIEW):
                 st = None
                 pull_request_pending_reviewers.append(r.user)
             pull_request_reviewers.append((r.user, st))
 
-        # calculate result
-        result = ChangesetStatus.STATUS_UNDER_REVIEW
-        if approved_votes and approved_votes == len(pull_request.reviewers):
-            result = ChangesetStatus.STATUS_APPROVED
+        result = self._calculate_status(relevant_statuses)
 
         return (pull_request_reviewers,
                 pull_request_pending_reviewers,
--- a/kallithea/model/comment.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/model/comment.py	Mon Jul 20 15:07:54 2015 +0200
@@ -63,10 +63,6 @@
                                line_no=None, revision=None, pull_request=None,
                                status_change=None, closing_pr=False):
         """
-        Get notification data
-
-        :param comment_text:
-        :param line:
         :returns: tuple (subj,body,recipients,notification_type,email_kwargs)
         """
         # make notification
@@ -104,7 +100,7 @@
             recipients += [cs_author]
             email_kwargs = {
                 'status_change': status_change,
-                'cs_comment_user': h.person(user, 'username_and_name'),
+                'cs_comment_user': h.person(user, 'full_name_and_username'),
                 'cs_target_repo': h.canonical_url('summary_home', repo_name=repo.repo_name),
                 'cs_comment_url': comment_url,
                 'raw_id': revision,
@@ -130,9 +126,9 @@
             comment_url = pull_request.url(canonical=True,
                 anchor='comment-%s' % comment.comment_id)
             subj = safe_unicode(
-                h.link_to('Re pull request #%(pr_id)s: %(desc)s %(line)s' % \
+                h.link_to('Re pull request %(pr_nice_id)s: %(desc)s %(line)s' % \
                           {'desc': desc,
-                           'pr_id': comment.pull_request.pull_request_id,
+                           'pr_nice_id': comment.pull_request.nice_id(),
                            'line': line},
                           comment_url)
             )
@@ -148,11 +144,11 @@
             #set some variables for email notification
             email_kwargs = {
                 'pr_title': pull_request.title,
-                'pr_id': pull_request.pull_request_id,
+                'pr_nice_id': pull_request.nice_id(),
                 'status_change': status_change,
                 'closing_pr': closing_pr,
                 'pr_comment_url': comment_url,
-                'pr_comment_user': h.person(user, 'username_and_name'),
+                'pr_comment_user': h.person(user, 'full_name_and_username'),
                 'pr_target_repo': h.canonical_url('summary_home',
                                    repo_name=pull_request.other_repo.repo_name),
                 'repo_name': pull_request.other_repo.repo_name,
@@ -167,24 +163,14 @@
                f_path=None, line_no=None, status_change=None, closing_pr=False,
                send_email=True):
         """
-        Creates new comment for changeset or pull request.
-        If status_change is not None this comment is associated with a
-        status change of changeset or changesets associated with pull request
+        Creates a new comment for either a changeset or a pull request.
+        status_change and closing_pr is only for the optional email.
 
-        :param text:
-        :param repo:
-        :param user:
-        :param revision:
-        :param pull_request: (for emails, not for comments)
-        :param f_path:
-        :param line_no:
-        :param status_change: (for emails, not for comments)
-        :param closing_pr: (for emails, not for comments)
-        :param send_email: also send email
+        Returns the created comment.
         """
-        if not text:
+        if not status_change and not text:
             log.warning('Missing text for comment, skipping...')
-            return
+            return None
 
         repo = self._get_repo(repo)
         user = self._get_user(user)
@@ -239,11 +225,6 @@
         return comment
 
     def delete(self, comment):
-        """
-        Deletes given comment
-
-        :param comment_id:
-        """
         comment = self.__get_changeset_comment(comment)
         Session().delete(comment)
 
@@ -251,33 +232,40 @@
 
     def get_comments(self, repo_id, revision=None, pull_request=None):
         """
-        Gets main comments based on revision or pull_request_id
-
-        :param repo_id:
-        :param revision:
-        :param pull_request:
-        """
+        Gets general comments for either revision or pull_request.
 
-        q = ChangesetComment.query()\
-                .filter(ChangesetComment.repo_id == repo_id)\
-                .filter(ChangesetComment.line_no == None)\
-                .filter(ChangesetComment.f_path == None)
-        if revision:
-            q = q.filter(ChangesetComment.revision == revision)
-        elif pull_request:
-            pull_request = self.__get_pull_request(pull_request)
-            q = q.filter(ChangesetComment.pull_request == pull_request)
-        else:
-            raise Exception('Please specify revision or pull_request')
-        q = q.order_by(ChangesetComment.created_on)
-        return q.all()
+        Returns a list, ordered by creation date.
+        """
+        return self._get_comments(repo_id, revision=revision, pull_request=pull_request,
+                                  inline=False)
 
     def get_inline_comments(self, repo_id, revision=None, pull_request=None):
+        """
+        Gets inline comments for either revision or pull_request.
+
+        Returns a list of tuples with file path and list of comments per line number.
+        """
+        comments = self._get_comments(repo_id, revision=revision, pull_request=pull_request,
+                                      inline=True)
+
+        paths = defaultdict(lambda: defaultdict(list))
+        for co in comments:
+            paths[co.f_path][co.line_no].append(co)
+        return paths.items()
+
+    def _get_comments(self, repo_id, revision=None, pull_request=None, inline=False):
+        """
+        Gets comments for either revision or pull_request_id, either inline or general.
+        """
         q = Session().query(ChangesetComment)\
-            .filter(ChangesetComment.repo_id == repo_id)\
-            .filter(ChangesetComment.line_no != None)\
-            .filter(ChangesetComment.f_path != None)\
-            .order_by(ChangesetComment.comment_id.asc())\
+            .filter(ChangesetComment.repo_id == repo_id)
+
+        if inline:
+            q = q.filter(ChangesetComment.line_no != None)\
+                .filter(ChangesetComment.f_path != None)
+        else:
+            q = q.filter(ChangesetComment.line_no == None)\
+                .filter(ChangesetComment.f_path == None)
 
         if revision:
             q = q.filter(ChangesetComment.revision == revision)
@@ -285,12 +273,6 @@
             pull_request = self.__get_pull_request(pull_request)
             q = q.filter(ChangesetComment.pull_request == pull_request)
         else:
-            raise Exception('Please specify revision or pull_request_id')
-
-        comments = q.all()
+            raise Exception('Please specify either revision or pull_request')
 
-        paths = defaultdict(lambda: defaultdict(list))
-
-        for co in comments:
-            paths[co.f_path][co.line_no].append(co)
-        return paths.items()
+        return q.order_by(ChangesetComment.created_on).all()
--- a/kallithea/model/db.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/model/db.py	Mon Jul 20 15:07:54 2015 +0200
@@ -43,6 +43,7 @@
 from pylons.i18n.translation import lazy_ugettext as _
 
 from kallithea import DB_PREFIX
+from kallithea.lib.exceptions import DefaultUserException
 from kallithea.lib.vcs import get_backend
 from kallithea.lib.vcs.utils.helpers import get_scm
 from kallithea.lib.vcs.exceptions import VCSError
@@ -175,8 +176,8 @@
     }
     DEFAULT_UPDATE_URL = ''
 
-    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    app_settings_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    app_settings_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
     _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None)
     _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
@@ -338,11 +339,11 @@
     HOOK_PULL = 'outgoing.pull_logger'
     HOOK_PRE_PULL = 'preoutgoing.pre_pull'
 
-    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
+    ui_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    ui_section = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_key = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_value = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_active = Column(Boolean(), nullable=True, unique=None, default=True)
 
     # def __init__(self, section='', key='', value=''):
     #     self.ui_section = section
@@ -401,20 +402,20 @@
     DEFAULT_USER = 'default'
     DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
 
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
-    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+    user_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    username = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    password = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    active = Column(Boolean(), nullable=True, unique=None, default=True)
+    admin = Column(Boolean(), nullable=True, unique=None, default=False)
     name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    lastname = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
     _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    last_login = Column(DateTime(timezone=False), nullable=True, unique=None, default=None)
+    extern_type = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    extern_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    api_key = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column(Boolean(), nullable=False, unique=None, default=True)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
     _user_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data
 
     user_log = relationship('UserLog')
@@ -438,7 +439,7 @@
     user_comments = relationship('ChangesetComment', cascade='all')
     #extra emails for this user
     user_emails = relationship('UserEmailMap', cascade='all')
-    #extra api keys
+    #extra API keys
     user_api_keys = relationship('UserApiKeys', cascade='all')
 
 
@@ -471,19 +472,28 @@
         return [x.ip_addr for x in ret]
 
     @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
     def full_name(self):
         return '%s %s' % (self.firstname, self.lastname)
 
     @property
     def full_name_or_username(self):
+        """
+        Show full name.
+        If full name is not set, fall back to username.
+        """
         return ('%s %s' % (self.firstname, self.lastname)
                 if (self.firstname and self.lastname) else self.username)
 
     @property
+    def full_name_and_username(self):
+        """
+        Show full name and username as 'Firstname Lastname (username)'.
+        If full name is not set, fall back to username.
+        """
+        return ('%s %s (%s)' % (self.firstname, self.lastname, self.username)
+                if (self.firstname and self.lastname) else self.username)
+
+    @property
     def full_contact(self):
         return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
 
@@ -526,6 +536,18 @@
                                       self.user_id, self.username)
 
     @classmethod
+    def get_or_404(cls, id_, allow_default=True):
+        '''
+        Overridden version of BaseModel.get_or_404, with an extra check on
+        the default user.
+        '''
+        user = super(User, cls).get_or_404(id_)
+        if allow_default == False:
+            if user.username == User.DEFAULT_USER:
+                raise DefaultUserException
+        return user
+
+    @classmethod
     def get_by_username(cls, username, case_insensitive=False, cache=False):
         if case_insensitive:
             q = cls.query().filter(cls.username.ilike(username))
@@ -678,12 +700,12 @@
     )
     __mapper_args__ = {}
 
-    user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True)
-    description = Column('description', UnicodeText(1024))
-    expires = Column('expires', Float(53), nullable=False)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_api_key_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    api_key = Column(String(255, convert_unicode=False), nullable=False, unique=True)
+    description = Column(UnicodeText(1024))
+    expires = Column(Float(53), nullable=False)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     user = relationship('User')
 
@@ -704,8 +726,8 @@
     )
     __mapper_args__ = {}
 
-    email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    email_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
     _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
     user = relationship('User')
 
@@ -735,10 +757,10 @@
     )
     __mapper_args__ = {}
 
-    ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    ip_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    ip_addr = Column(String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    active = Column(Boolean(), nullable=True, unique=None, default=True)
     user = relationship('User')
 
     @classmethod
@@ -763,14 +785,14 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
-    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
-    repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
+    user_log_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    username = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    repository_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    user_ip = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    action = Column(UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None)
+    action_date = Column(DateTime(timezone=False), nullable=True, unique=None, default=None)
 
     def __unicode__(self):
         return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
@@ -792,13 +814,13 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
 
-    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
-    user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
-    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
+    users_group_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    users_group_name = Column(String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    user_group_description = Column(String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    users_group_active = Column(Boolean(), nullable=True, unique=None, default=None)
     inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
     _group_data = Column("group_data", LargeBinary(), nullable=True)  # JSON data
 
     members = relationship('UserGroupMember', cascade="all, delete-orphan")
@@ -882,9 +904,9 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
 
-    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    users_group_member_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
     users_group = relationship('UserGroup')
@@ -903,14 +925,14 @@
     )
     PREFIX = 'ex_'  # prefix used in form to not conflict with already existing fields
 
-    repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-    field_key = Column("field_key", String(250, convert_unicode=False))
-    field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False)
-    field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False)
-    field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False)
-    field_type = Column("field_type", String(255), nullable=False, unique=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    repo_field_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    field_key = Column(String(250, convert_unicode=False))
+    field_label = Column(String(1024, convert_unicode=False), nullable=False)
+    field_value = Column(String(10000, convert_unicode=False), nullable=False)
+    field_desc = Column(String(1024, convert_unicode=False), nullable=False)
+    field_type = Column(String(255), nullable=False, unique=None)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     repository = relationship('Repository')
 
@@ -947,26 +969,26 @@
     STATE_PENDING = 'repo_state_pending'
     STATE_ERROR = 'repo_state_error'
 
-    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
-    repo_state = Column("repo_state", String(255), nullable=True)
-
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
-    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
+    repo_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    repo_name = Column(String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    repo_state = Column(String(255), nullable=True)
+
+    clone_uri = Column(String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    repo_type = Column(String(255, convert_unicode=False), nullable=False, unique=False, default=None)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    private = Column(Boolean(), nullable=True, unique=None, default=None)
     enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
     enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
-    description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    description = Column(String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    created_on = Column(DateTime(timezone=False), nullable=False, unique=None, default=datetime.datetime.now)
+    updated_on = Column(DateTime(timezone=False), nullable=False, unique=None, default=datetime.datetime.now)
     _landing_revision = Column("landing_revision", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    enable_locking = Column(Boolean(), nullable=False, unique=None, default=False)
     _locked = Column("locked", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
     _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
 
-    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
+    fork_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
+    group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
 
     user = relationship('User')
     fork = relationship('Repository', remote_side=repo_id)
@@ -1280,7 +1302,7 @@
                 pass
 
         return get_clone_url(uri_tmpl=uri_tmpl,
-                             qualifed_home_url=qualified_home_url,
+                             qualified_home_url=qualified_home_url,
                              repo_name=self.repo_name,
                              repo_id=self.repo_id, **override)
 
@@ -1387,12 +1409,13 @@
 
         grouped = {}
         for stat in statuses.all():
-            pr_id = pr_repo = None
+            pr_id = pr_nice_id = pr_repo = None
             if stat.pull_request:
                 pr_id = stat.pull_request.pull_request_id
+                pr_nice_id = PullRequest.make_nice_id(pr_id)
                 pr_repo = stat.pull_request.other_repo.repo_name
             grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
+                                      pr_id, pr_repo, pr_nice_id]
         return grouped
 
     def _repo_size(self):
@@ -1466,13 +1489,13 @@
 
     SEP = ' &raquo; '
 
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    group_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    group_name = Column(String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    group_parent_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
+    group_description = Column(String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    enable_locking = Column(Boolean(), nullable=False, unique=None, default=False)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
     users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
@@ -1500,7 +1523,7 @@
 
         repo_groups = []
         if show_empty_group:
-            repo_groups = [('-1', u'-- %s --' % _('top level'))]
+            repo_groups = [('-1', u'-- %s --' % _('Top level'))]
 
         repo_groups.extend([cls._generate_choice(x) for x in groups])
 
@@ -1731,9 +1754,9 @@
         'hg.create.repository': 1
     }
 
-    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    permission_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    permission_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    permission_longname = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
     def __unicode__(self):
         return u"<%s('%s:%s')>" % (
@@ -1779,10 +1802,10 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    repo_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
     repository = relationship('Repository')
@@ -1808,10 +1831,10 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_user_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
     user_group = relationship('UserGroup')
@@ -1837,9 +1860,9 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
     permission = relationship('Permission')
@@ -1855,10 +1878,10 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    users_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 
     users_group = relationship('UserGroup')
     permission = relationship('Permission')
@@ -1885,10 +1908,10 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_group_user_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    target_user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
 
     target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
     user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
@@ -1914,9 +1937,9 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    users_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
     users_group = relationship('UserGroup')
     permission = relationship('Permission')
@@ -1930,10 +1953,10 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
 
-    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
     group = relationship('RepoGroup')
@@ -1957,10 +1980,10 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
 
-    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    users_group_repo_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
     users_group = relationship('UserGroup')
     permission = relationship('Permission')
@@ -1983,12 +2006,12 @@
          {'extend_existing': True, 'mysql_engine': 'InnoDB',
           'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
-    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
-    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
-    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
-    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
+    stat_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+    stat_on_revision = Column(Integer(), nullable=False)
+    commit_activity = Column(LargeBinary(1000000), nullable=False)#JSON data
+    commit_activity_combined = Column(LargeBinary(), nullable=False)#JSON data
+    languages = Column(LargeBinary(1000000), nullable=False)#JSON data
 
     repository = relationship('Repository', single_parent=True)
 
@@ -2002,11 +2025,11 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
 
-    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    user_following_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
     follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    follows_user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    follows_from = Column(DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
 
     user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
 
@@ -2027,14 +2050,14 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
     # cache_id, not used
-    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    cache_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
     # cache_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    cache_key = Column(String(255, convert_unicode=False))
     # cache_args is a repo_name
-    cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    # instance sets cache_active True when it is caching,
-    # other instances set cache_active to False to indicate that this cache is invalid
-    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+    cache_args = Column(String(255, convert_unicode=False))
+    # instance sets cache_active True when it is caching, other instances set
+    # cache_active to False to indicate that this cache is invalid
+    cache_active = Column(Boolean(), nullable=True, unique=None, default=False)
 
     def __init__(self, cache_key, repo_name=''):
         self.cache_key = cache_key
@@ -2042,8 +2065,9 @@
         self.cache_active = False
 
     def __unicode__(self):
-        return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
-                            self.cache_id, self.cache_key, self.cache_active)
+        return u"<%s('%s:%s[%s]')>" % (
+            self.__class__.__name__,
+            self.cache_id, self.cache_key, self.cache_active)
 
     def _cache_key_partition(self):
         prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
@@ -2146,24 +2170,25 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
-    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
-    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    revision = Column('revision', String(40), nullable=True)
-    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
-    line_no = Column('line_no', Unicode(10), nullable=True)
-    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
-    f_path = Column('f_path', Unicode(1000), nullable=True)
-    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
-    text = Column('text', UnicodeText(25000), nullable=False)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    comment_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    revision = Column(String(40))
+    pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'))
+    line_no = Column(Unicode(10))
+    hl_lines = Column(Unicode(512))
+    f_path = Column(Unicode(1000))
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
+    text = Column(UnicodeText(25000), nullable=False)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     author = relationship('User')
     repo = relationship('Repository')
     # status_change is frequently used directly in templates - make it a lazy
     # join to avoid fetching each related ChangesetStatus on demand.
     # There will only be one ChangesetStatus referencing each comment so the join will not explode.
-    status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
+    status_change = relationship('ChangesetStatus',
+                                 cascade="all, delete-orphan", lazy='joined')
     pull_request = relationship('PullRequest')
 
     @classmethod
@@ -2183,6 +2208,13 @@
             q = q.filter(cls.pull_request_id == pull_request_id)
         return q.all()
 
+    def url(self):
+        anchor = "comment-%s" % self.comment_id
+        import kallithea.lib.helpers as h
+        if self.revision:
+            return h.url('changeset_home', repo_name=self.repo.repo_name, revision=self.revision, anchor=anchor)
+        elif self.pull_request_id is not None:
+            return self.pull_request.url(anchor=anchor)
 
 class ChangesetStatus(Base, BaseModel):
     __tablename__ = 'changeset_statuses'
@@ -2208,15 +2240,15 @@
         (STATUS_UNDER_REVIEW, _("Under Review")),
     ]
 
-    changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
-    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
-    revision = Column('revision', String(40), nullable=False)
-    status = Column('status', String(128), nullable=False, default=DEFAULT)
-    changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
-    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
-    version = Column('version', Integer(), nullable=False, default=0)
-    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+    changeset_status_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    revision = Column(String(40), nullable=False)
+    status = Column(String(128), nullable=False, default=DEFAULT)
+    changeset_comment_id = Column(Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
+    modified_at = Column(DateTime(), nullable=False, default=datetime.datetime.now)
+    version = Column(Integer(), nullable=False, default=0)
+    pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
 
     author = relationship('User')
     repo = relationship('Repository')
@@ -2251,18 +2283,18 @@
     STATUS_NEW = u'new'
     STATUS_CLOSED = u'closed'
 
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(255), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    pull_request_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    title = Column(Unicode(255), nullable=True)
+    description = Column(UnicodeText(10240))
+    status = Column(Unicode(255), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
     _revisions = Column('revisions', UnicodeText(20500))  # 500 revisions max
-    org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    org_ref = Column('org_ref', Unicode(255), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(255), nullable=False)
+    org_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    org_ref = Column(Unicode(255), nullable=False)
+    other_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    other_ref = Column(Unicode(255), nullable=False)
 
     @hybrid_property
     def revisions(self):
@@ -2306,6 +2338,15 @@
             .first()
         return str(status.status) if status else ''
 
+    @classmethod
+    def make_nice_id(cls, pull_request_id):
+        '''Return pull request id nicely formatted for displaying'''
+        return '#%s' % pull_request_id
+
+    def nice_id(self):
+        '''Return the id of this pull request, nicely formatted for displaying'''
+        return self.make_nice_id(self.pull_request_id)
+
     def __json__(self):
         return dict(
             revisions=self.revisions
@@ -2337,9 +2378,9 @@
         self.user = user
         self.pull_request = pull_request
 
-    pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
-    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
+    pull_requests_reviewers_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
 
     user = relationship('User')
     pull_request = relationship('PullRequest')
@@ -2360,11 +2401,11 @@
     TYPE_PULL_REQUEST = u'pull_request'
     TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
 
-    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
-    subject = Column('subject', Unicode(512), nullable=True)
-    body = Column('body', UnicodeText(50000), nullable=True)
-    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    notification_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    subject = Column(Unicode(512), nullable=True)
+    body = Column(UnicodeText(50000), nullable=True)
+    created_by = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
     type_ = Column('type', Unicode(255))
 
     created_by_user = relationship('User')
@@ -2372,8 +2413,8 @@
 
     @property
     def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
+        return [x.user for x in UserNotification.query()
+                .filter(UserNotification.notification == self)
                 .order_by(UserNotification.user_id.asc()).all()]
 
     @classmethod
@@ -2410,10 +2451,10 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
-    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
-    read = Column('read', Boolean, default=False)
-    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), primary_key=True)
+    notification_id = Column(Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
+    read = Column(Boolean, default=False)
+    sent_on = Column(DateTime(timezone=False), nullable=True, unique=None)
 
     user = relationship('User')
     notification = relationship('Notification')
@@ -2435,14 +2476,14 @@
     GIST_PRIVATE = u'private'
     DEFAULT_FILENAME = u'gistfile1.txt'
 
-    gist_id = Column('gist_id', Integer(), primary_key=True)
-    gist_access_id = Column('gist_access_id', Unicode(250))
-    gist_description = Column('gist_description', UnicodeText(1024))
+    gist_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    gist_access_id = Column(Unicode(250))
+    gist_description = Column(UnicodeText(1024))
     gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
-    gist_expires = Column('gist_expires', Float(53), nullable=False)
-    gist_type = Column('gist_type', Unicode(128), nullable=False)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    gist_expires = Column(Float(53), nullable=False)
+    gist_type = Column(Unicode(128), nullable=False)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     owner = relationship('User')
 
@@ -2519,6 +2560,6 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
-    repository_id = Column('repository_id', String(250), primary_key=True)
-    repository_path = Column('repository_path', Text)
-    version = Column('version', Integer)
+    repository_id = Column(String(250), nullable=False, unique=True, primary_key=True)
+    repository_path = Column(Text)
+    version = Column(Integer)
--- a/kallithea/model/notification.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/model/notification.py	Mon Jul 20 15:07:54 2015 +0200
@@ -248,25 +248,31 @@
         """
         #alias
         _n = notification
-        _map = {
-            _n.TYPE_CHANGESET_COMMENT: _('%(user)s commented on changeset at %(when)s'),
-            _n.TYPE_MESSAGE: _('%(user)s sent message at %(when)s'),
-            _n.TYPE_MENTION: _('%(user)s mentioned you at %(when)s'),
-            _n.TYPE_REGISTRATION: _('%(user)s registered in Kallithea at %(when)s'),
-            _n.TYPE_PULL_REQUEST: _('%(user)s opened new pull request at %(when)s'),
-            _n.TYPE_PULL_REQUEST_COMMENT: _('%(user)s commented on pull request at %(when)s')
-        }
-        tmpl = _map[notification.type_]
 
         if show_age:
-            when = h.age(notification.created_on)
+            return {
+                    _n.TYPE_CHANGESET_COMMENT: _('%(user)s commented on changeset %(age)s'),
+                    _n.TYPE_MESSAGE: _('%(user)s sent message %(age)s'),
+                    _n.TYPE_MENTION: _('%(user)s mentioned you %(age)s'),
+                    _n.TYPE_REGISTRATION: _('%(user)s registered in Kallithea %(age)s'),
+                    _n.TYPE_PULL_REQUEST: _('%(user)s opened new pull request %(age)s'),
+                    _n.TYPE_PULL_REQUEST_COMMENT: _('%(user)s commented on pull request %(age)s'),
+                }[notification.type_] % dict(
+                    user=notification.created_by_user.username,
+                    age=h.age(notification.created_on),
+                )
         else:
-            when = h.fmt_date(notification.created_on)
-
-        return tmpl % dict(
-            user=notification.created_by_user.username,
-            when=when,
-            )
+            return {
+                    _n.TYPE_CHANGESET_COMMENT: _('%(user)s commented on changeset at %(when)s'),
+                    _n.TYPE_MESSAGE: _('%(user)s sent message at %(when)s'),
+                    _n.TYPE_MENTION: _('%(user)s mentioned you at %(when)s'),
+                    _n.TYPE_REGISTRATION: _('%(user)s registered in Kallithea at %(when)s'),
+                    _n.TYPE_PULL_REQUEST: _('%(user)s opened new pull request at %(when)s'),
+                    _n.TYPE_PULL_REQUEST_COMMENT: _('%(user)s commented on pull request at %(when)s'),
+                }[notification.type_] % dict(
+                    user=notification.created_by_user.username,
+                    when=h.fmt_date(notification.created_on),
+                )
 
 
 class EmailNotificationModel(BaseModel):
@@ -293,13 +299,13 @@
             self.TYPE_PULL_REQUEST_COMMENT: 'pull_request_comment',
         }
         self._subj_map = {
-            self.TYPE_CHANGESET_COMMENT: _('Comment on %(repo_name)s changeset %(short_id)s on %(branch)s by %(comment_username)s'),
+            self.TYPE_CHANGESET_COMMENT: _('[Comment from %(comment_username)s] %(repo_name)s changeset %(short_id)s on %(branch)s'),
             self.TYPE_MESSAGE: 'Test Message',
             # self.TYPE_PASSWORD_RESET
             self.TYPE_REGISTRATION: _('New user %(new_username)s registered'),
             # self.TYPE_DEFAULT
-            self.TYPE_PULL_REQUEST: _('Review request on %(repo_name)s pull request #%(pr_id)s from %(ref)s by %(pr_username)s'),
-            self.TYPE_PULL_REQUEST_COMMENT: _('Comment on %(repo_name)s pull request #%(pr_id)s from %(ref)s by %(comment_username)s'),
+            self.TYPE_PULL_REQUEST: _('[Added by %(pr_username)s] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s'),
+            self.TYPE_PULL_REQUEST_COMMENT: _('[Comment from %(comment_username)s] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s'),
         }
 
     def get_email_description(self, type_, **kwargs):
@@ -314,7 +320,10 @@
             raise
         l = [safe_unicode(x) for x in [kwargs.get('status_change'), kwargs.get('closing_pr') and _('Closing')] if x]
         if l:
-            subj += ' (%s)' % (', '.join(l))
+            if subj.startswith('['):
+                subj = '[' + ', '.join(l) + ': ' + subj[1:]
+            else:
+                subj = '[' + ', '.join(l) + '] ' + subj
         return subj
 
     def get_email_tmpl(self, type_, content_type, **kwargs):
--- a/kallithea/model/pull_request.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/model/pull_request.py	Mon Jul 20 15:07:54 2015 +0200
@@ -32,6 +32,7 @@
 
 from kallithea.model.meta import Session
 from kallithea.lib import helpers as h
+from kallithea.lib.exceptions import UserInvalidException
 from kallithea.model import BaseModel
 from kallithea.model.db import PullRequest, PullRequestReviewers, Notification,\
     ChangesetStatus, User
@@ -93,11 +94,12 @@
         #reset state to under-review
         from kallithea.model.comment import ChangesetCommentsModel
         comment = ChangesetCommentsModel().create(
-            text=u'Auto status change to %s' % (ChangesetStatus.get_status_lbl(ChangesetStatus.STATUS_UNDER_REVIEW)),
+            text=u'',
             repo=org_repo,
             user=new.author,
             pull_request=new,
-            send_email=False
+            send_email=False,
+            status_change=ChangesetStatus.STATUS_UNDER_REVIEW,
         )
         ChangesetStatusModel().set_status(
             org_repo,
@@ -117,6 +119,8 @@
         #members
         for member in set(reviewers):
             _usr = self._get_user(member)
+            if _usr is None:
+                raise UserInvalidException(member)
             reviewer = PullRequestReviewers(_usr, pr)
             Session().add(reviewer)
 
@@ -130,10 +134,10 @@
                                       h.canonical_hostname())]
         subject = safe_unicode(
             h.link_to(
-              _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
+              _('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') % \
                 {'user': pr.author.username,
                  'pr_title': pr.title,
-                 'pr_id': pr.pull_request_id},
+                 'pr_nice_id': pr.nice_id()},
                 pr_url)
             )
         body = pr.description
@@ -145,7 +149,7 @@
             'pr_url': pr_url,
             'pr_revisions': revision_data,
             'repo_name': pr.other_repo.repo_name,
-            'pr_id': pr.pull_request_id,
+            'pr_nice_id': pr.nice_id(),
             'ref': org_ref_name,
             'pr_username': pr.author.username,
             'threading': threading,
--- a/kallithea/model/repo.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/model/repo.py	Mon Jul 20 15:07:54 2015 +0200
@@ -209,10 +209,7 @@
                            cs_cache.get('message'))
 
         def desc(desc):
-            if c.visual.stylify_metatags:
-                return h.urlify_text(h.desc_stylize(h.html_escape(h.truncate(desc, 60))))
-            else:
-                return h.urlify_text(h.html_escape(h.truncate(desc, 60)))
+            return h.urlify_text(desc, truncate=60, stylize=c.visual.stylify_metatags)
 
         def state(repo_state):
             return _render("repo_state", repo_state)
--- a/kallithea/model/user.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/model/user.py	Mon Jul 20 15:07:54 2015 +0200
@@ -60,16 +60,7 @@
         return self._get_user(user)
 
     def get_by_username(self, username, cache=False, case_insensitive=False):
-
-        if case_insensitive:
-            user = self.sa.query(User).filter(User.username.ilike(username))
-        else:
-            user = self.sa.query(User)\
-                .filter(User.username == username)
-        if cache:
-            user = user.options(FromCache("sql_cache_short",
-                                          "get_user_%s" % username))
-        return user.scalar()
+        return User.get_by_username(username, case_insensitive, cache)
 
     def get_by_email(self, email, cache=False, case_insensitive=False):
         return User.get_by_email(email, case_insensitive, cache)
@@ -81,12 +72,17 @@
         if not cur_user:
             cur_user = getattr(get_current_authuser(), 'username', None)
 
-        from kallithea.lib.hooks import log_create_user, check_allowed_create_user
+        from kallithea.lib.hooks import log_create_user, \
+            check_allowed_create_user
         _fd = form_data
         user_data = {
-            'username': _fd['username'], 'password': _fd['password'],
-            'email': _fd['email'], 'firstname': _fd['firstname'], 'lastname': _fd['lastname'],
-            'active': _fd['active'], 'admin': False
+            'username': _fd['username'],
+            'password': _fd['password'],
+            'email': _fd['email'],
+            'firstname': _fd['firstname'],
+            'lastname': _fd['lastname'],
+            'active': _fd['active'],
+            'admin': False
         }
         # raises UserCreationError if it's not allowed
         check_allowed_create_user(user_data, cur_user)
@@ -128,7 +124,8 @@
             cur_user = getattr(get_current_authuser(), 'username', None)
 
         from kallithea.lib.auth import get_crypt_password, check_password
-        from kallithea.lib.hooks import log_create_user, check_allowed_create_user
+        from kallithea.lib.hooks import log_create_user, \
+            check_allowed_create_user
         user_data = {
             'username': username, 'password': password,
             'email': email, 'firstname': firstname, 'lastname': lastname,
@@ -153,8 +150,10 @@
             new_user.admin = admin
             new_user.email = email
             new_user.active = active
-            new_user.extern_name = safe_unicode(extern_name) if extern_name else None
-            new_user.extern_type = safe_unicode(extern_type) if extern_type else None
+            new_user.extern_name = safe_unicode(extern_name) \
+                if extern_name else None
+            new_user.extern_type = safe_unicode(extern_type) \
+                if extern_type else None
             new_user.name = firstname
             new_user.lastname = lastname
 
@@ -162,12 +161,13 @@
                 new_user.api_key = generate_api_key()
 
             # set password only if creating an user or password is changed
-            password_change = new_user.password and not check_password(password,
-                                                            new_user.password)
+            password_change = new_user.password and \
+                not check_password(password, new_user.password)
             if not edit or password_change:
                 reason = 'new password' if edit else 'new user'
                 log.debug('Updating password reason=>%s' % (reason,))
-                new_user.password = get_crypt_password(password) if password else None
+                new_user.password = get_crypt_password(password) \
+                    if password else None
 
             self.sa.add(new_user)
 
@@ -192,14 +192,17 @@
 
         # notification to admins
         subject = _('New user registration')
-        body = ('New user registration\n'
-                '---------------------\n'
-                '- Username: %s\n'
-                '- Full Name: %s\n'
-                '- Email: %s\n')
-        body = body % (new_user.username, new_user.full_name, new_user.email)
+        body = (
+            'New user registration\n'
+            '---------------------\n'
+            '- Username: {user.username}\n'
+            '- Full Name: {user.full_name}\n'
+            '- Email: {user.email}\n'
+            ).format(user=new_user)
         edit_url = h.canonical_url('edit_user', id=new_user.user_id)
-        email_kwargs = {'registered_user_url': edit_url, 'new_username': new_user.username}
+        email_kwargs = {
+            'registered_user_url': edit_url,
+            'new_username': new_user.username}
         NotificationModel().create(created_by=new_user, subject=subject,
                                    body=body, recipients=None,
                                    type_=Notification.TYPE_REGISTRATION,
@@ -253,29 +256,25 @@
         if user.username == User.DEFAULT_USER:
             raise DefaultUserException(
                 _(u"You can't remove this user since it's"
-                  " crucial for entire application")
-            )
+                  " crucial for entire application"))
         if user.repositories:
             repos = [x.repo_name for x in user.repositories]
             raise UserOwnsReposException(
                 _(u'User "%s" still owns %s repositories and cannot be '
                   'removed. Switch owners or remove those repositories: %s')
-                % (user.username, len(repos), ', '.join(repos))
-            )
+                % (user.username, len(repos), ', '.join(repos)))
         if user.repo_groups:
             repogroups = [x.group_name for x in user.repo_groups]
-            raise UserOwnsReposException(
-                _(u'User "%s" still owns %s repository groups and cannot be '
-                  'removed. Switch owners or remove those repository groups: %s')
-                % (user.username, len(repogroups), ', '.join(repogroups))
-            )
+            raise UserOwnsReposException(_(
+                'User "%s" still owns %s repository groups and cannot be '
+                'removed. Switch owners or remove those repository groups: %s')
+                % (user.username, len(repogroups), ', '.join(repogroups)))
         if user.user_groups:
             usergroups = [x.users_group_name for x in user.user_groups]
             raise UserOwnsReposException(
-                _(u'User "%s" still owns %s user groups and cannot be '
+                _('User "%s" still owns %s user groups and cannot be '
                   'removed. Switch owners or remove those user groups: %s')
-                % (user.username, len(usergroups), ', '.join(usergroups))
-            )
+                % (user.username, len(usergroups), ', '.join(usergroups)))
         self.sa.delete(user)
 
         from kallithea.lib.hooks import log_delete_user
@@ -290,16 +289,17 @@
         user = User.get_by_email(user_email)
         if user:
             log.debug('password reset user found %s' % user)
-            link = h.canonical_url('reset_password_confirmation', key=user.api_key)
+            link = h.canonical_url('reset_password_confirmation',
+                                   key=user.api_key)
             reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
-            body = EmailNotificationModel().get_email_tmpl(reg_type,
-                                                           'txt',
-                                                           user=user.short_contact,
-                                                           reset_url=link)
-            html_body = EmailNotificationModel().get_email_tmpl(reg_type,
-                                                           'html',
-                                                           user=user.short_contact,
-                                                           reset_url=link)
+            body = EmailNotificationModel().get_email_tmpl(
+                reg_type, 'txt',
+                user=user.short_contact,
+                reset_url=link)
+            html_body = EmailNotificationModel().get_email_tmpl(
+                reg_type, 'html',
+                user=user.short_contact,
+                reset_url=link)
             log.debug('sending email')
             run_task(tasks.send_email, [user_email],
                      _("Password reset link"), body, html_body)
@@ -314,8 +314,8 @@
         from kallithea.lib import auth
         user_email = data['email']
         user = User.get_by_email(user_email)
-        new_passwd = auth.PasswordGenerator().gen_password(8,
-                        auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
+        new_passwd = auth.PasswordGenerator().gen_password(
+            8, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
         if user:
             user.password = auth.get_crypt_password(new_passwd)
             Session().add(user)
@@ -340,7 +340,7 @@
 
         :param auth_user: instance of user to set attributes
         :param user_id: user id to fetch by
-        :param api_key: api key to fetch by
+        :param api_key: API key to fetch by
         :param username: username to fetch by
         """
         if user_id is None and api_key is None and username is None:
@@ -401,12 +401,10 @@
         user = self._get_user(user)
         perm = self._get_perm(perm)
 
-        obj = UserToPerm.query()\
-                .filter(UserToPerm.user == user)\
-                .filter(UserToPerm.permission == perm)\
-                .scalar()
-        if obj:
-            self.sa.delete(obj)
+        UserToPerm.query().filter(
+            UserToPerm.user == user,
+            UserToPerm.permission == perm,
+        ).delete()
 
     def add_extra_email(self, user, email):
         """
@@ -440,7 +438,7 @@
 
     def add_extra_ip(self, user, ip):
         """
-        Adds ip address to UserIpMap
+        Adds IP address to UserIpMap
 
         :param user:
         :param ip:
@@ -458,7 +456,7 @@
 
     def delete_extra_ip(self, user, ip_id):
         """
-        Removes ip address from UserIpMap
+        Removes IP address from UserIpMap
 
         :param user:
         :param ip_id:
--- a/kallithea/model/validators.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/model/validators.py	Mon Jul 20 15:07:54 2015 +0200
@@ -458,7 +458,7 @@
 
         elif repo_type == 'git':
             from kallithea.lib.vcs.backends.git.repository import GitRepository
-            if url.startswith('http'):
+            if url.startswith('http') or url.startswith('git'):
                 # initially check if it's at least the proper URL
                 # or does it pass basic auth
                 GitRepository._check_url(url)
@@ -471,9 +471,9 @@
 
     class _validator(formencode.validators.FancyValidator):
         messages = {
-            'clone_uri': _(u'invalid clone URL'),
-            'invalid_clone_uri': _(u'Invalid clone URL, provide a '
-                                    'valid clone http(s)/svn+http(s)/ssh URL')
+            'clone_uri': _(u'Invalid repository URL'),
+            'invalid_clone_uri': _(u'Invalid repository URL. It must be a '
+                                    'valid http, https, ssh, svn+http or svn+https URL'),
         }
 
         def validate_python(self, value, state):
@@ -814,7 +814,7 @@
 def ValidIp():
     class _validator(CIDR):
         messages = dict(
-            badFormat=_('Please enter a valid IPv4 or IpV6 address'),
+            badFormat=_('Please enter a valid IPv4 or IPv6 address'),
             illegalBits=_('The network size (bits) must be within the range'
                 ' of 0-32 (not %(bits)r)')
         )
@@ -836,7 +836,7 @@
         def validate_python(self, value, state):
             try:
                 addr = value.strip()
-                #this raises an ValueError if address is not IpV4 or IpV6
+                #this raises an ValueError if address is not IPv4 or IPv6
                 ipaddr.IPNetwork(address=addr)
             except ValueError:
                 raise formencode.Invalid(self.message('badFormat', state),
--- a/kallithea/public/css/contextbar.css	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/public/css/contextbar.css	Mon Jul 20 15:07:54 2015 +0200
@@ -19,19 +19,19 @@
 
 .icon-diff-M:before {
     font-family: 'kallithea';
-    content: '\e805';
+    content: '\22a1';
     color: #d0b44c;
 }
 
 .icon-diff-D:before {
     font-family: 'kallithea';
-    content: '\e807';
+    content: '\229f';
     color: #bd2c00;
 }
 
 .icon-diff-A:before {
     font-family: 'kallithea';
-    content: '\e806';
+    content: '\229e';
     color: #6cc644;
 }
 
@@ -279,7 +279,8 @@
 #revision-changer:before,
 #context-pages a.childs:after,
 #context-pages a.dropdown:after {
-    content: ' \25BE';
+    font-family: 'kallithea';
+    content: ' \23f7';
 }
 #context-pages a.childs {
     padding-right: 30px;
--- a/kallithea/public/css/style.css	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/public/css/style.css	Mon Jul 20 15:07:54 2015 +0200
@@ -162,7 +162,7 @@
 div.rst-block pre,
 .CodeMirror .CodeMirror-code pre {
     font-size: 12px;
-    font-family: Consolas, Monaco, Inconsolata, Liberation Mono, monospace;
+    font-family: Lucida Console, Consolas, Monaco, Inconsolata, Liberation Mono, monospace;
 }
 
 .top-left-rounded-corner {
@@ -253,7 +253,8 @@
 h4:hover > a.permalink,
 h5:hover > a.permalink,
 h6:hover > a.permalink,
-div:hover > a.permalink {
+div:hover > a.permalink,
+div:hover > span > a.permalink {
     visibility: visible;
 }
 
@@ -510,7 +511,7 @@
 
 td.quick_repo_menu:before {
     font-family: "kallithea";
-    content: "\e80f";           /* triangle-right */
+    content: "\23f5";           /* triangle-right */
     margin-left: 3px;
     padding-right: 3px;
 }
@@ -523,7 +524,7 @@
 
 td.quick_repo_menu.active:before {
     font-family: "kallithea";
-    content: "\e80d";           /* triangle-down */
+    content: "\23f7";           /* triangle-down */
     margin-left: 1px;
     padding-right: 1px;
 }
@@ -735,12 +736,12 @@
 
 .yui-skin-sam th.yui-dt-asc .yui-dt-liner:after {
     font-family: "kallithea";
-    content: "\e810";           /* triangle-up */
+    content: "\23f6";           /* triangle-up */
 }
 
 .yui-skin-sam th.yui-dt-desc .yui-dt-liner:after {
     font-family: "kallithea";
-    content: "\e80d";           /* triangle-down */
+    content: "\23f7";           /* triangle-down */
 }
 
 tbody .yui-dt-editable { cursor: pointer }
@@ -1871,7 +1872,7 @@
     padding: 20px;
 }
 
-#login div.form div.fields div.field div.label {
+#login div.form div.form-horizontal div.form-group > label {
     width: 173px;
     float: left;
     text-align: right;
@@ -1879,7 +1880,7 @@
     padding: 5px 0 0 5px;
 }
 
-#login div.form div.fields div.field div.input input {
+#login div.form div.form-horizontal div.form-group div input {
     background: #FFF;
     border-top: 1px solid #b3b3b3;
     border-left: 1px solid #b3b3b3;
@@ -1891,20 +1892,16 @@
     padding: 7px 7px 6px;
 }
 
-#login div.form div.fields div.buttons {
-    clear: both;
-    overflow: hidden;
-    border-top: 1px solid #DDD;
-    text-align: right;
-    margin: 0;
-    padding: 10px 0 0;
+#login div.form .buttons {
+    float: right;
 }
 
 #login div.form div.links {
     clear: both;
     overflow: hidden;
     margin: 10px 0 0;
-    padding: 0 0 2px;
+    border-top: 1px solid #DDD;
+    padding: 10px 0 0;
 }
 
 .user-menu {
@@ -2310,6 +2307,14 @@
     margin-right: -3px;
 }
 
+.changeset-logical-index {
+    color: #666666;
+    font-style: italic;
+    font-size: 85%;
+    padding-right: 0.5em;
+    text-align: right;
+}
+
 .changeset_hash {
     color: #000000;
 }
@@ -2690,15 +2695,11 @@
     float: left;
     padding: 2px 0px 0px 2px;
 }
-.changeset-status-container .changeset-status-lbl {
-    float: left;
-    padding: 3px 4px 0px 0px
-}
 .code-header .changeset-status-container .changeset-status-lbl {
     float: left;
     padding: 0px 4px 0px 0px;
 }
-.changeset-status-container .changeset-status-ico {
+.changeset-status-container div.changeset-status-ico {
     float: left;
 }
 .code-header .changeset-status-container .changeset-status-ico,
@@ -3719,7 +3720,8 @@
 }
 
 .repo-switcher .select2-chosen:after {
-    content: ' \25BE';
+    font-family: 'kallithea';
+    content: ' \23f7';
 }
 
 .repo-switcher-dropdown.select2-drop.select2-drop-active {
@@ -3758,8 +3760,8 @@
 }
 
 #content div.box div.form div.fields,
-#login div.form,
-#login div.form div.fields,
+#login div.form-horizontal,
+#login div.form-horizontal div.form-group,
 #register div.form,
 #register div.form div.fields {
     clear: both;
@@ -3769,7 +3771,7 @@
 }
 
 #content div.box div.form div.fields div.field div.label span,
-#login div.form div.fields div.field div.label span,
+#login div.form div.form-horizontal div.form-group div.label span,
 #register div.form div.fields div.field div.label span {
     height: 1%;
     display: block;
@@ -3779,7 +3781,7 @@
 }
 
 #content div.box div.form div.fields div.field div.input input.error,
-#login div.form div.fields div.field div.input input.error,
+#login div.form div.form-horizontal div.form-group div.input input.error,
 #register div.form div.fields div.field div.input input.error {
     background: #FBE3E4;
     border-top: 1px solid #e1b2b3;
@@ -3789,7 +3791,7 @@
 }
 
 #content div.box div.form div.fields div.field div.input input.success,
-#login div.form div.fields div.field div.input input.success,
+#login div.form div.form-horizontal div.form-group div.input input.success,
 #register div.form div.fields div.field div.input input.success {
     background: #E6EFC2;
     border-top: 1px solid #cebb98;
@@ -3891,7 +3893,7 @@
 }
 
 #content div.box div.action div.button,
-#login div.form div.fields div.field div.input div.link,
+#login div.form div.form-horizontal div.form-group div.input div.link,
 #register div.form div.fields div.field div.input div.link {
     text-align: right;
     margin: 6px 0 0;
@@ -3960,7 +3962,7 @@
     padding: 0;
 }
 
-#login div.form div.fields div.field,
+#login div.form div.form-horizontal div.form-group,
 #register div.form div.fields div.field {
     clear: both;
     overflow: hidden;
@@ -3968,7 +3970,7 @@
     padding: 0 0 10px;
 }
 
-#login div.form div.fields div.field span.error-message,
+#login div.form div.form-horizontal div.form-group span.error-message,
 #register div.form div.fields div.field span.error-message {
     height: 1%;
     display: block;
@@ -3978,36 +3980,36 @@
     max-width: 320px;
 }
 
-#login div.form div.fields div.field div.label label,
+#login div.form div.form-horizontal div.form-group label,
 #register div.form div.fields div.field div.label label {
     color: #000;
     font-weight: 700;
 }
 
-#login div.form div.fields div.field div.input,
+#login div.form div.form-horizontal div.form-group div,
 #register div.form div.fields div.field div.input {
     float: left;
     margin: 0;
     padding: 0;
 }
 
-#login div.form div.fields div.field div.input input.large {
+#login div.form div.form-horizontal div.form-group div input.large {
     width: 250px;
 }
 
-#login div.form div.fields div.field div.checkbox,
+#login div.form div.form-horizontal div.form-group div.checkbox,
 #register div.form div.fields div.field div.checkbox {
     margin: 0 0 0 184px;
     padding: 0;
 }
 
-#login div.form div.fields div.field div.checkbox label,
+#login div.form div.form-horizontal div.form-group div.checkbox label,
 #register div.form div.fields div.field div.checkbox label {
     color: #565656;
     font-weight: 700;
 }
 
-#login div.form div.fields div.buttons input,
+#login div.form div.buttons input,
 #register div.form div.fields div.buttons input {
     color: #000;
     font-size: 1em;
@@ -4222,7 +4224,7 @@
     clear: both;
     overflow: hidden;
     margin: 0;
-    padding: 0 20px 10px;
+    padding: 0 20px;
 }
 
 div.rst-block h1,
@@ -4350,6 +4352,10 @@
     font-size: 16px;
 }
 
+.automatic-comment {
+    font-style: italic;
+}
+
 /** comment form **/
 
 .status-block {
@@ -4365,7 +4371,7 @@
 .comment-form textarea {
     width: 100%;
     height: 100px;
-    font-family: Consolas, Monaco, Inconsolata, Liberation Mono, monospace;
+    font-family: Lucida Console, Consolas, Monaco, Inconsolata, Liberation Mono, monospace;
 }
 
 form.comment-form {
@@ -4463,7 +4469,7 @@
 .comment-inline-form textarea {
     width: 100%;
     height: 100px;
-    font-family: Consolas, Monaco, Inconsolata, Liberation Mono, monospace;
+    font-family: Lucida Console, Consolas, Monaco, Inconsolata, Liberation Mono, monospace;
 }
 
 form.comment-inline-form {
@@ -4925,7 +4931,7 @@
 }
 table.code-difftable .lineno a {
     color: #aaa !important;
-    font: 11px Consolas, Monaco, Inconsolata, Liberation Mono, monospace !important;
+    font: 11px Lucida Console, Consolas, Monaco, Inconsolata, Liberation Mono, monospace !important;
     letter-spacing: -1px;
     padding-left: 10px;
     padding-right: 8px;
@@ -4972,7 +4978,7 @@
     position: relative;
     width: 0;
 }
- 
+
 table.code-difftable .add .code pre:before {
     content: "+";
     color: #080;
@@ -4981,7 +4987,7 @@
     position: relative;
     width: 0;
 }
- 
+
 table.code-difftable .unmod .code pre:before {
     content: " ";
     float: left;
@@ -4989,7 +4995,7 @@
     position: relative;
     width: 0;
 }
- 
+
 .add-bubble {
     position: relative;
     display: none;
@@ -5002,9 +5008,10 @@
     box-sizing: border-box;
 }
 
-tr.line.add:hover td .add-bubble,
-tr.line.del:hover td .add-bubble,
-tr.line.unmod:hover td .add-bubble {
+/* comment bubble, only visible when in a commentable diff */
+.commentable-diff tr.line.add:hover td .add-bubble,
+.commentable-diff tr.line.del:hover td .add-bubble,
+.commentable-diff tr.line.unmod:hover td .add-bubble {
     display: block;
     z-index: 1;
 }
@@ -5026,7 +5033,7 @@
     font-size: 14px;
     color: #ffffff;
     font-family: "kallithea";
-    content: '\e80c';
+    content: '\1f5ea';
 }
 
 .add-bubble div:hover {
@@ -5067,8 +5074,8 @@
     margin: 5px;
 }
 
-div.prev-next-comment div.prev-comment,
-div.prev-next-comment div.next-comment {
+div.comment-prev-next-links div.prev-comment,
+div.comment-prev-next-links div.next-comment {
     display: inline-block;
     min-width: 150px;
     margin: 3px 6px;
--- a/kallithea/public/fontello/config.json	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/public/fontello/config.json	Mon Jul 20 15:07:54 2015 +0200
@@ -401,7 +401,7 @@
     {
       "uid": "c04672812513206682a859166c207252",
       "css": "clippy",
-      "code": 59419,
+      "code": 128203,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -457,7 +457,7 @@
     {
       "uid": "c9e14800494dc6027b3dcee0817cf984",
       "css": "code",
-      "code": 59425,
+      "code": 8596,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -485,7 +485,7 @@
     {
       "uid": "7aeda197b5b0fba630669530807d736d",
       "css": "comment",
-      "code": 59403,
+      "code": 128489,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -499,7 +499,7 @@
     {
       "uid": "28655242f029e97efd1bf5379e22c4ba",
       "css": "comment-discussion",
-      "code": 59404,
+      "code": 128490,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -555,7 +555,7 @@
     {
       "uid": "776af96a4db73c51c13fd256f12d6f40",
       "css": "database",
-      "code": 59392,
+      "code": 9923,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -639,7 +639,7 @@
     {
       "uid": "ccc8f787411138446b85c9fe52e33b8b",
       "css": "diff-added",
-      "code": 59398,
+      "code": 8862,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -667,7 +667,7 @@
     {
       "uid": "2672a84540d3632649f01108e2db0341",
       "css": "diff-modified",
-      "code": 59397,
+      "code": 8865,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -681,7 +681,7 @@
     {
       "uid": "1ce39df7ee1648c4054633b371546367",
       "css": "diff-removed",
-      "code": 59399,
+      "code": 8863,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -905,7 +905,7 @@
     {
       "uid": "c59600c892bd7e46476f38e89812fbbf",
       "css": "gear",
-      "code": 59441,
+      "code": 9965,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -1031,7 +1031,7 @@
     {
       "uid": "b00e349d8883c0b89796940a9913e8e8",
       "css": "globe",
-      "code": 59410,
+      "code": 127758,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -1283,7 +1283,7 @@
     {
       "uid": "3617d121a15ef91892d5083aeead6ce3",
       "css": "key",
-      "code": 59430,
+      "code": 128273,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -1591,7 +1591,7 @@
     {
       "uid": "ef6263a57a35ecee2f8ddda2fe20dcab",
       "css": "move-down",
-      "code": 59395,
+      "code": 11123,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -1633,7 +1633,7 @@
     {
       "uid": "13e7ebf511552da48a5bc1d2e7cf6de2",
       "css": "move-up",
-      "code": 59396,
+      "code": 11121,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2123,7 +2123,7 @@
     {
       "uid": "809bb7ea7bf6ca7e94029eae402f922f",
       "css": "search",
-      "code": 59435,
+      "code": 128269,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2333,7 +2333,7 @@
     {
       "uid": "92dd16866a2367537f47bec5ee11484f",
       "css": "tools",
-      "code": 59400,
+      "code": 128736,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2347,7 +2347,7 @@
     {
       "uid": "448bfec39235117b6a7e3e8d46aa184f",
       "css": "trashcan",
-      "code": 59433,
+      "code": 128465,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2361,7 +2361,7 @@
     {
       "uid": "ebbb1978bedc8e562ab0d462a832919f",
       "css": "triangle-down",
-      "code": 59405,
+      "code": 9207,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2375,7 +2375,7 @@
     {
       "uid": "814cb1fe473de29d20b3d4e366ed434b",
       "css": "triangle-left",
-      "code": 59406,
+      "code": 9204,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2389,7 +2389,7 @@
     {
       "uid": "d191392505c6a27bbeafd2d283a18d23",
       "css": "triangle-right",
-      "code": 59407,
+      "code": 9205,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2403,7 +2403,7 @@
     {
       "uid": "14bdc9ddaebbaecf5bad552a14cae637",
       "css": "triangle-up",
-      "code": 59408,
+      "code": 9206,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2501,7 +2501,7 @@
     {
       "uid": "16b70cc2a5fbff7de2cbf73498e25b5c",
       "css": "keyhole-circled",
-      "code": 59426,
+      "code": 128272,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2543,91 +2543,91 @@
     {
       "uid": "d73eceadda1f594cec0536087539afbf",
       "css": "heart",
-      "code": 59436,
+      "code": 9829,
       "src": "fontawesome"
     },
     {
       "uid": "f3dc2d6d8fe9cf9ebff84dc260888cdf",
       "css": "heart-empty",
-      "code": 59437,
+      "code": 9825,
       "src": "fontawesome"
     },
     {
       "uid": "8b80d36d4ef43889db10bc1f0dc9a862",
       "css": "user",
-      "code": 59438,
+      "code": 128100,
       "src": "fontawesome"
     },
     {
       "uid": "31972e4e9d080eaa796290349ae6c1fd",
       "css": "users",
-      "code": 59439,
+      "code": 128101,
       "src": "fontawesome"
     },
     {
       "uid": "12f4ece88e46abd864e40b35e05b11cd",
       "css": "ok",
-      "code": 59440,
+      "code": 128504,
       "src": "fontawesome"
     },
     {
       "uid": "43ab845088317bd348dee1d975700c48",
       "css": "ok-circled",
-      "code": 59402,
+      "code": 128505,
       "src": "fontawesome"
     },
     {
       "uid": "5211af474d3a9848f67f945e2ccaf143",
       "css": "cancel",
-      "code": 59434,
+      "code": 128500,
       "src": "fontawesome"
     },
     {
       "uid": "0f4cae16f34ae243a6144c18a003f2d8",
       "css": "cancel-circled",
-      "code": 59401,
+      "code": 128501,
       "src": "fontawesome"
     },
     {
       "uid": "44e04715aecbca7f266a17d5a7863c68",
       "css": "plus",
-      "code": 59471,
+      "code": 10133,
       "src": "fontawesome"
     },
     {
       "uid": "4ba33d2607902cf690dd45df09774cb0",
       "css": "plus-circled",
-      "code": 59468,
+      "code": 8853,
       "src": "fontawesome"
     },
     {
       "uid": "861ab06e455e2de3232ebef67d60d708",
       "css": "minus",
-      "code": 59472,
+      "code": 10134,
       "src": "fontawesome"
     },
     {
       "uid": "eeadb020bb75d089b25d8424aabe19e0",
       "css": "minus-circled",
-      "code": 59469,
+      "code": 8854,
       "src": "fontawesome"
     },
     {
       "uid": "c1f1975c885aa9f3dad7810c53b82074",
       "css": "lock",
-      "code": 59450,
+      "code": 128274,
       "src": "fontawesome"
     },
     {
       "uid": "05376be04a27d5a46e855a233d6e8508",
       "css": "lock-open-alt",
-      "code": 59451,
+      "code": 128275,
       "src": "fontawesome"
     },
     {
       "uid": "c5fd349cbd3d23e4ade333789c29c729",
       "css": "eye",
-      "code": 59474,
+      "code": 128065,
       "src": "fontawesome"
     },
     {
@@ -2639,7 +2639,7 @@
     {
       "uid": "3db5347bd219f3bce6025780f5d9ef45",
       "css": "tag",
-      "code": 59452,
+      "code": 128278,
       "src": "fontawesome"
     },
     {
@@ -2675,25 +2675,25 @@
     {
       "uid": "d35a1d35efeb784d1dc9ac18b9b6c2b6",
       "css": "pencil",
-      "code": 59458,
+      "code": 128393,
       "src": "fontawesome"
     },
     {
       "uid": "44fae3bfdd54754dc68ec50d37efea37",
       "css": "pencil-squared",
-      "code": 59460,
+      "code": 128394,
       "src": "fontawesome"
     },
     {
       "uid": "41087bc74d4b20b55059c60a33bf4008",
       "css": "edit",
-      "code": 59461,
+      "code": 128395,
       "src": "fontawesome"
     },
     {
       "uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
       "css": "doc",
-      "code": 59443,
+      "code": 128453,
       "src": "fontawesome"
     },
     {
@@ -2705,7 +2705,7 @@
     {
       "uid": "5408be43f7c42bccee419c6be53fdef5",
       "css": "doc-text",
-      "code": 59445,
+      "code": 128456,
       "src": "fontawesome"
     },
     {
@@ -2729,25 +2729,25 @@
     {
       "uid": "f8aa663c489bcbd6e68ec8147dca841e",
       "css": "folder",
-      "code": 59475,
+      "code": 128448,
       "src": "fontawesome"
     },
     {
       "uid": "c95735c17a10af81448c7fed98a04546",
       "css": "folder-open",
-      "code": 59462,
+      "code": 128449,
       "src": "fontawesome"
     },
     {
       "uid": "b091a8bd0fdade174951f17d936f51e4",
       "css": "folder-empty",
-      "code": 59476,
+      "code": 128193,
       "src": "fontawesome"
     },
     {
       "uid": "6533bdc16ab201eb3f3b27ce989cab33",
       "css": "folder-open-empty",
-      "code": 59463,
+      "code": 128194,
       "src": "fontawesome"
     },
     {
@@ -2771,13 +2771,13 @@
     {
       "uid": "98687378abd1faf8f6af97c254eb6cd6",
       "css": "cog-alt",
-      "code": 59442,
+      "code": 9881,
       "src": "fontawesome"
     },
     {
       "uid": "5bb103cd29de77e0e06a52638527b575",
       "css": "wrench",
-      "code": 59479,
+      "code": 128295,
       "src": "fontawesome"
     },
     {
@@ -2789,19 +2789,19 @@
     {
       "uid": "598a5f2bcf3521d1615de8e1881ccd17",
       "css": "clock",
-      "code": 59449,
+      "code": 8986,
       "src": "fontawesome"
     },
     {
       "uid": "98d9c83c1ee7c2c25af784b518c522c5",
       "css": "block",
-      "code": 59470,
+      "code": 128683,
       "src": "fontawesome"
     },
     {
       "uid": "d3b3f17bc3eb7cd809a07bbd4d178bee",
       "css": "resize-vertical",
-      "code": 59415,
+      "code": 11109,
       "src": "fontawesome"
     },
     {
@@ -2825,25 +2825,25 @@
     {
       "uid": "ccddff8e8670dcd130e3cb55fdfc2fd0",
       "css": "down-open",
-      "code": 59464,
+      "code": 8595,
       "src": "fontawesome"
     },
     {
       "uid": "d870630ff8f81e6de3958ecaeac532f2",
       "css": "left-open",
-      "code": 59465,
+      "code": 8594,
       "src": "fontawesome"
     },
     {
       "uid": "399ef63b1e23ab1b761dfbb5591fa4da",
       "css": "right-open",
-      "code": 59466,
+      "code": 8592,
       "src": "fontawesome"
     },
     {
       "uid": "fe6697b391355dec12f3d86d6d490397",
       "css": "up-open",
-      "code": 59467,
+      "code": 8593,
       "src": "fontawesome"
     },
     {
@@ -2879,13 +2879,13 @@
     {
       "uid": "a73c5deb486c8d66249811642e5d719a",
       "css": "arrows-cw",
-      "code": 59448,
+      "code": 128472,
       "src": "fontawesome"
     },
     {
       "uid": "6020aff067fc3c119cdd75daa5249220",
       "css": "exchange",
-      "code": 59483,
+      "code": 8644,
       "src": "fontawesome"
     },
     {
@@ -2909,25 +2909,25 @@
     {
       "uid": "9755f76110ae4d12ac5f9466c9152031",
       "css": "book",
-      "code": 59454,
+      "code": 128210,
       "src": "fontawesome"
     },
     {
       "uid": "130380e481a7defc690dfb24123a1f0c",
       "css": "circle",
-      "code": 59485,
+      "code": 8226,
       "src": "fontawesome"
     },
     {
       "uid": "422e07e5afb80258a9c4ed1706498f8a",
       "css": "circle-empty",
-      "code": 59484,
+      "code": 9702,
       "src": "fontawesome"
     },
     {
       "uid": "f4445feb55521283572ee88bc304f928",
       "css": "floppy",
-      "code": 59480,
+      "code": 128190,
       "src": "fontawesome"
     },
     {
@@ -2939,7 +2939,7 @@
     {
       "uid": "56a21935a5d4d79b2e91ec00f760b369",
       "css": "sort",
-      "code": 59482,
+      "code": 8597,
       "src": "fontawesome"
     }
   ]
--- a/kallithea/public/fontello/css/kallithea.css	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/public/fontello/css/kallithea.css	Mon Jul 20 15:07:54 2015 +0200
@@ -1,10 +1,12 @@
-@font-face {
+@charset "UTF-8";
+
+ @font-face {
   font-family: 'kallithea';
-  src: url('../font/kallithea.eot?81834162');
-  src: url('../font/kallithea.eot?81834162#iefix') format('embedded-opentype'),
-       url('../font/kallithea.woff?81834162') format('woff'),
-       url('../font/kallithea.ttf?81834162') format('truetype'),
-       url('../font/kallithea.svg?81834162#kallithea') format('svg');
+  src: url('../font/kallithea.eot?23730278');
+  src: url('../font/kallithea.eot?23730278#iefix') format('embedded-opentype'),
+       url('../font/kallithea.woff?23730278') format('woff'),
+       url('../font/kallithea.ttf?23730278') format('truetype'),
+       url('../font/kallithea.svg?23730278#kallithea') format('svg');
   font-weight: normal;
   font-style: normal;
 }
@@ -14,7 +16,7 @@
 @media screen and (-webkit-min-device-pixel-ratio:0) {
   @font-face {
     font-family: 'kallithea';
-    src: url('../font/kallithea.svg?81834162#kallithea') format('svg');
+    src: url('../font/kallithea.svg?23730278#kallithea') format('svg');
   }
 }
 */
@@ -35,7 +37,7 @@
   /* For safety - reset parent styles, that can break glyph codes*/
   font-variant: normal;
   text-transform: none;
-     
+ 
   /* fix buttons height, for twitter bootstrap */
   line-height: 1em;
  
@@ -46,102 +48,73 @@
   /* you can be more comfortable with increased icons size */
   /* font-size: 120%; */
  
+  /* Font smoothing. That was taken from TWBS */
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+ 
   /* Uncomment for 3D effect */
   /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
 }
  
-.icon-database:before { content: '\e800'; } /* '' */
+.icon-circle:before { content: '\2022'; } /* '•' */
+.icon-right-open:before { content: '\2190'; } /* '←' */
+.icon-up-open:before { content: '\2191'; } /* '↑' */
+.icon-left-open:before { content: '\2192'; } /* '→' */
+.icon-down-open:before { content: '\2193'; } /* '↓' */
+.icon-code:before { content: '\2194'; } /* '↔' */
+.icon-sort:before { content: '\2195'; } /* '↕' */
+.icon-exchange:before { content: '\21c4'; } /* '⇄' */
+.icon-plus-circled:before { content: '\2295'; } /* '⊕' */
+.icon-minus-circled:before { content: '\2296'; } /* '⊖' */
+.icon-diff-added:before { content: '\229e'; } /* '⊞' */
+.icon-diff-removed:before { content: '\229f'; } /* '⊟' */
+.icon-diff-modified:before { content: '\22a1'; } /* '⊡' */
+.icon-clock:before { content: '\231a'; } /* '⌚' */
+.icon-triangle-left:before { content: '\23f4'; } /* '⏴' */
+.icon-triangle-right:before { content: '\23f5'; } /* '⏵' */
+.icon-triangle-up:before { content: '\23f6'; } /* '⏶' */
+.icon-triangle-down:before { content: '\23f7'; } /* '⏷' */
+.icon-circle-empty:before { content: '\25e6'; } /* '◦' */
+.icon-heart-empty:before { content: '\2661'; } /* '♡' */
+.icon-heart:before { content: '\2665'; } /* '♥' */
+.icon-cog-alt:before { content: '\2699'; } /* '⚙' */
+.icon-database:before { content: '\26c3'; } /* '⛃' */
+.icon-gear:before { content: '\26ed'; } /* '⛭' */
+.icon-plus:before { content: '\2795'; } /* '➕' */
+.icon-minus:before { content: '\2796'; } /* '➖' */
+.icon-resize-vertical:before { content: '\2b65'; } /* '⭥' */
+.icon-move-up:before { content: '\2b71'; } /* '⭱' */
+.icon-move-down:before { content: '\2b73'; } /* '⭳' */
 .icon-file-submodule:before { content: '\e801'; } /* '' */
 .icon-git-merge:before { content: '\e802'; } /* '' */
-.icon-move-down:before { content: '\e803'; } /* '' */
-.icon-move-up:before { content: '\e804'; } /* '' */
-.icon-diff-modified:before { content: '\e805'; } /* '' */
-.icon-diff-added:before { content: '\e806'; } /* '' */
-.icon-diff-removed:before { content: '\e807'; } /* '' */
-.icon-tools:before { content: '\e808'; } /* '' */
-.icon-cancel-circled:before { content: '\e809'; } /* '' */
-.icon-ok-circled:before { content: '\e80a'; } /* '' */
-.icon-comment:before { content: '\e80b'; } /* '' */
-.icon-comment-discussion:before { content: '\e80c'; } /* '' */
-.icon-triangle-down:before { content: '\e80d'; } /* '' */
-.icon-triangle-left:before { content: '\e80e'; } /* '' */
-.icon-triangle-right:before { content: '\e80f'; } /* '' */
-.icon-triangle-up:before { content: '\e810'; } /* '' */
 .icon-ruler:before { content: '\e811'; } /* '' */
-.icon-globe:before { content: '\e812'; } /* '' */
 .icon-download-cloud:before { content: '\e813'; } /* '' */
 .icon-upload-cloud:before { content: '\e814'; } /* '' */
 .icon-graph:before { content: '\e815'; } /* '' */
 .icon-file-zip:before { content: '\e816'; } /* '' */
-.icon-resize-vertical:before { content: '\e817'; } /* '' */
 .icon-file-code:before { content: '\e81a'; } /* '' */
-.icon-clippy:before { content: '\e81b'; } /* '' */
 .icon-doc-text-inv:before { content: '\e81c'; } /* '' */
 .icon-diff:before { content: '\e81d'; } /* '' */
 .icon-diff-ignored:before { content: '\e81e'; } /* '' */
 .icon-diff-renamed:before { content: '\e81f'; } /* '' */
 .icon-paste:before { content: '\e820'; } /* '' */
-.icon-code:before { content: '\e821'; } /* '' */
-.icon-keyhole-circled:before { content: '\e822'; } /* '' */
 .icon-file-directory:before { content: '\e823'; } /* '' */
 .icon-git-compare:before { content: '\e824'; } /* '' */
 .icon-git-pull-request:before { content: '\e825'; } /* '' */
-.icon-key:before { content: '\e826'; } /* '' */
 .icon-repo-forked:before { content: '\e827'; } /* '' */
 .icon-fork:before { content: '\e828'; } /* '' */
-.icon-trashcan:before { content: '\e829'; } /* '' */
-.icon-cancel:before { content: '\e82a'; } /* '' */
-.icon-search:before { content: '\e82b'; } /* '' */
-.icon-heart:before { content: '\e82c'; } /* '' */
-.icon-heart-empty:before { content: '\e82d'; } /* '' */
-.icon-user:before { content: '\e82e'; } /* '' */
-.icon-users:before { content: '\e82f'; } /* '' */
-.icon-ok:before { content: '\e830'; } /* '' */
-.icon-gear:before { content: '\e831'; } /* '' */
-.icon-cog-alt:before { content: '\e832'; } /* '' */
-.icon-doc:before { content: '\e833'; } /* '' */
 .icon-docs:before { content: '\e834'; } /* '' */
-.icon-doc-text:before { content: '\e835'; } /* '' */
 .icon-doc-inv:before { content: '\e836'; } /* '' */
 .icon-file-powerpoint:before { content: '\e837'; } /* '' */
-.icon-arrows-cw:before { content: '\e838'; } /* '' */
-.icon-clock:before { content: '\e839'; } /* '' */
-.icon-lock:before { content: '\e83a'; } /* '' */
-.icon-lock-open-alt:before { content: '\e83b'; } /* '' */
-.icon-tag:before { content: '\e83c'; } /* '' */
 .icon-tags:before { content: '\e83d'; } /* '' */
-.icon-book:before { content: '\e83e'; } /* '' */
 .icon-bookmark-empty:before { content: '\e83f'; } /* '' */
 .icon-bookmark:before { content: '\e840'; } /* '' */
 .icon-align-left:before { content: '\e841'; } /* '' */
-.icon-pencil:before { content: '\e842'; } /* '' */
 .icon-sliders:before { content: '\e843'; } /* '' */
-.icon-pencil-squared:before { content: '\e844'; } /* '' */
-.icon-edit:before { content: '\e845'; } /* '' */
-.icon-folder-open:before { content: '\e846'; } /* '' */
-.icon-folder-open-empty:before { content: '\e847'; } /* '' */
-.icon-down-open:before { content: '\e848'; } /* '' */
-.icon-left-open:before { content: '\e849'; } /* '' */
-.icon-right-open:before { content: '\e84a'; } /* '' */
-.icon-up-open:before { content: '\e84b'; } /* '' */
-.icon-plus-circled:before { content: '\e84c'; } /* '' */
-.icon-minus-circled:before { content: '\e84d'; } /* '' */
-.icon-block:before { content: '\e84e'; } /* '' */
-.icon-plus:before { content: '\e84f'; } /* '' */
-.icon-minus:before { content: '\e850'; } /* '' */
 .icon-eye-off:before { content: '\e851'; } /* '' */
-.icon-eye:before { content: '\e852'; } /* '' */
-.icon-folder:before { content: '\e853'; } /* '' */
-.icon-folder-empty:before { content: '\e854'; } /* '' */
 .icon-rss:before { content: '\e855'; } /* '' */
 .icon-rss-squared:before { content: '\e856'; } /* '' */
-.icon-wrench:before { content: '\e857'; } /* '' */
-.icon-floppy:before { content: '\e858'; } /* '' */
 .icon-strike:before { content: '\e859'; } /* '' */
-.icon-sort:before { content: '\e85a'; } /* '' */
-.icon-exchange:before { content: '\e85b'; } /* '' */
-.icon-circle-empty:before { content: '\e85c'; } /* '' */
-.icon-circle:before { content: '\e85d'; } /* '' */
 .icon-box:before { content: '\e85e'; } /* '' */
 .icon-right:before { content: '\e85f'; } /* '' */
 .icon-left:before { content: '\e860'; } /* '' */
@@ -150,4 +123,37 @@
 .icon-up-circled:before { content: '\e863'; } /* '' */
 .icon-up:before { content: '\e864'; } /* '' */
 .icon-down:before { content: '\e865'; } /* '' */
-.icon-down-circled:before { content: '\e866'; } /* '' */
\ No newline at end of file
+.icon-down-circled:before { content: '\e866'; } /* '' */
+.icon-globe:before { content: '🌎'; } /* '\1f30e' */
+.icon-eye:before { content: '👁'; } /* '\1f441' */
+.icon-user:before { content: '👤'; } /* '\1f464' */
+.icon-users:before { content: '👥'; } /* '\1f465' */
+.icon-floppy:before { content: '💾'; } /* '\1f4be' */
+.icon-folder-empty:before { content: '📁'; } /* '\1f4c1' */
+.icon-folder-open-empty:before { content: '📂'; } /* '\1f4c2' */
+.icon-clippy:before { content: '📋'; } /* '\1f4cb' */
+.icon-book:before { content: '📒'; } /* '\1f4d2' */
+.icon-search:before { content: '🔍'; } /* '\1f50d' */
+.icon-keyhole-circled:before { content: '🔐'; } /* '\1f510' */
+.icon-key:before { content: '🔑'; } /* '\1f511' */
+.icon-lock:before { content: '🔒'; } /* '\1f512' */
+.icon-lock-open-alt:before { content: '🔓'; } /* '\1f513' */
+.icon-tag:before { content: '🔖'; } /* '\1f516' */
+.icon-wrench:before { content: '🔧'; } /* '\1f527' */
+.icon-pencil:before { content: '🖉'; } /* '\1f589' */
+.icon-pencil-squared:before { content: '🖊'; } /* '\1f58a' */
+.icon-edit:before { content: '🖋'; } /* '\1f58b' */
+.icon-folder:before { content: '🗀'; } /* '\1f5c0' */
+.icon-folder-open:before { content: '🗁'; } /* '\1f5c1' */
+.icon-doc:before { content: '🗅'; } /* '\1f5c5' */
+.icon-doc-text:before { content: '🗈'; } /* '\1f5c8' */
+.icon-trashcan:before { content: '🗑'; } /* '\1f5d1' */
+.icon-arrows-cw:before { content: '🗘'; } /* '\1f5d8' */
+.icon-comment:before { content: '🗩'; } /* '\1f5e9' */
+.icon-comment-discussion:before { content: '🗪'; } /* '\1f5ea' */
+.icon-cancel:before { content: '🗴'; } /* '\1f5f4' */
+.icon-cancel-circled:before { content: '🗵'; } /* '\1f5f5' */
+.icon-ok:before { content: '🗸'; } /* '\1f5f8' */
+.icon-ok-circled:before { content: '🗹'; } /* '\1f5f9' */
+.icon-block:before { content: '🚫'; } /* '\1f6ab' */
+.icon-tools:before { content: '🛠'; } /* '\1f6e0' */
\ No newline at end of file
Binary file kallithea/public/fontello/font/kallithea.eot has changed
--- a/kallithea/public/fontello/font/kallithea.svg	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/public/fontello/font/kallithea.svg	Mon Jul 20 15:07:54 2015 +0200
@@ -6,98 +6,65 @@
 <font id="kallithea" horiz-adv-x="1000" >
 <font-face font-family="kallithea" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
 <missing-glyph horiz-adv-x="1000" />
-<glyph glyph-name="database" unicode="&#xe800;" d="m375-88c-207 0-375 56-375 126 0 37 0 79 0 125 0 10 5 21 13 31 42-54 187-94 362-94s320 40 362 94c8-10 13-21 13-31 0-37 0-75 0-125 0-70-168-126-375-126z m0 251c-207 0-375 56-375 125 0 37 0 78 0 125 0 6 3 13 6 19l0 0c2 4 4 8 7 12 42-54 187-94 362-94s320 40 362 94c3-4 5-8 7-12l0 0c4-6 6-13 6-19 0-37 0-75 0-125 0-69-168-125-375-125z m0 250c-207 0-375 56-375 125 0 19 0 40 0 62 0 20 0 41 0 63 0 69 168 125 375 125 207 0 375-56 375-125 0-20 0-41 0-63 0-19 0-39 0-62 0-69-168-125-375-125z m0 312c-138 0-250-28-250-62s112-63 250-63 250 28 250 63-112 62-250 62z" horiz-adv-x="750" />
+<glyph glyph-name="circle" unicode="&#x2022;" d="m857 350q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="right-open" unicode="&#x2190;" d="m618 361l-414-415q-11-10-25-10t-26 10l-92 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l92 93q11 10 26 10t25-10l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
+<glyph glyph-name="up-open" unicode="&#x2191;" d="m939 107l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-26 10l-92 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
+<glyph glyph-name="left-open" unicode="&#x2192;" d="m653 682l-296-296 296-297q11-10 11-25t-11-25l-92-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 10 25 10t25-10l92-93q11-10 11-25t-11-25z" horiz-adv-x="714.3" />
+<glyph glyph-name="down-open" unicode="&#x2193;" d="m939 399l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l92 92q11 11 26 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" />
+<glyph glyph-name="code" unicode="&#x2194;" d="m594 663l-94-94 219-219-219-219 94-93 281 312-281 313z m-313 0l-281-313 281-312 94 93-219 219 219 219-94 94z" horiz-adv-x="875" />
+<glyph glyph-name="sort" unicode="&#x2195;" d="m571 243q0-15-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 10-11 25t11 25 25 11h500q14 0 25-11t10-25z m0 214q0-14-10-25t-25-11h-500q-15 0-25 11t-11 25 11 25l250 250q10 11 25 11t25-11l250-250q10-10 10-25z" horiz-adv-x="571.4" />
+<glyph glyph-name="exchange" unicode="&#x21c4;" d="m1000 189v-107q0-7-5-12t-13-6h-768v-107q0-7-5-12t-13-6q-6 0-13 6l-178 178q-5 5-5 13 0 8 5 13l179 178q5 5 12 5 8 0 13-5t5-13v-107h768q7 0 13-5t5-13z m0 304q0-8-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107h-768q-7 0-13 6t-5 12v107q0 8 5 13t13 5h768v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z" horiz-adv-x="1000" />
+<glyph glyph-name="plus-circled" unicode="&#x2295;" d="m679 314v72q0 14-11 25t-25 10h-143v143q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-143h-143q-14 0-25-10t-10-25v-72q0-14 10-25t25-11h143v-142q0-15 11-25t25-11h71q15 0 25 11t11 25v142h143q14 0 25 11t11 25z m178 36q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="minus-circled" unicode="&#x2296;" d="m679 314v72q0 14-11 25t-25 10h-429q-14 0-25-10t-10-25v-72q0-14 10-25t25-10h429q14 0 25 10t11 25z m178 36q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="diff-added" unicode="&#x229e;" d="m500 538h-125v-125h-125v-125h125v-125h125v125h125v125h-125v125z m313 250c-32 0-719 0-750 0s-63-32-63-63 0-719 0-750 31-63 63-63 718 0 750 0 62 32 62 63 0 719 0 750-31 63-62 63z m-63-719c0-16-17-31-31-31s-545 0-563 0-31 11-31 31c0 15 0 547 0 562s16 32 31 32 548 0 563 0 31-16 31-32 0-547 0-562z" horiz-adv-x="875" />
+<glyph glyph-name="diff-removed" unicode="&#x229f;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-500 219h375v125h-375v-125z" horiz-adv-x="875" />
+<glyph glyph-name="diff-modified" unicode="&#x22a1;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-312 406c-70 0-125-56-125-125s55-125 125-125 125 56 125 125-56 125-125 125z" horiz-adv-x="875" />
+<glyph glyph-name="clock" unicode="&#x231a;" d="m500 546v-250q0-7-5-12t-13-5h-178q-8 0-13 5t-5 12v36q0 8 5 13t13 5h125v196q0 8 5 13t12 5h36q8 0 13-5t5-13z m232-196q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="triangle-left" unicode="&#x23f4;" d="m0 350l375-375v750l-375-375z" horiz-adv-x="374.8" />
+<glyph glyph-name="triangle-right" unicode="&#x23f5;" d="m0 725l375-375-375-375v750z" horiz-adv-x="374.9" />
+<glyph glyph-name="triangle-up" unicode="&#x23f6;" d="m375 600l-375-375h750l-375 375z" horiz-adv-x="749.5" />
+<glyph glyph-name="triangle-down" unicode="&#x23f7;" d="m0 475l375-375 375 375h-750z" horiz-adv-x="749.5" />
+<glyph glyph-name="circle-empty" unicode="&#x25e6;" d="m429 654q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152-41 152-110 111-152 41z m428-304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="heart-empty" unicode="&#x2661;" d="m929 517q0 46-12 80t-31 55-46 33-52 18-55 4-62-14-62-36-48-40-34-34q-10-13-27-13t-27 13q-14 15-34 34t-48 40-62 36-62 14-55-4-52-18-46-33-31-55-12-80q0-93 105-198l324-312 324 312q105 105 105 198z m71 0q0-123-128-251l-347-335q-10-10-25-10t-25 10l-348 336q-5 5-15 15t-31 36-38 55-30 67-13 77q0 123 71 192t196 70q34 0 70-12t67-33 54-38 42-38q20 20 42 38t54 38 67 33 70 12q125 0 196-70t71-192z" horiz-adv-x="1000" />
+<glyph glyph-name="heart" unicode="&#x2665;" d="m500-79q-14 0-25 10l-348 336q-5 5-15 15t-31 36-38 55-30 67-13 77q0 123 71 192t196 70q34 0 70-12t67-33 54-38 42-38q20 20 42 38t54 38 67 33 70 12q125 0 196-70t71-192q0-123-128-251l-347-335q-10-10-25-10z" horiz-adv-x="1000" />
+<glyph glyph-name="cog-alt" unicode="&#x2699;" d="m500 350q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m429-286q0 29-22 51t-50 21-50-21-21-51q0-29 21-50t50-21 51 21 21 50z m0 572q0 29-22 50t-50 21-50-21-21-50q0-30 21-51t50-21 51 21 21 51z m-215-235v-103q0-6-4-11t-9-6l-86-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-13-17-46-50t-44-33q-6 0-11 4l-64 50q-21-11-43-17-6-60-13-87-4-13-17-13h-104q-6 0-11 4t-5 10l-13 85q-19 6-42 18l-66-50q-4-4-11-4-6 0-12 4-80 75-80 90 0 5 4 10 5 8 23 30t26 34q-13 24-20 46l-85 13q-5 1-9 5t-4 11v103q0 6 4 11t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 11 12 17 46 50t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 41-17l66 49q5 4 11 4 7 0 12-4 81-75 81-90 0-5-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q5-2 9-6t4-11z m357-298v-78q0-9-83-17-6-15-16-29 28-63 28-77 0-2-2-4-68-40-69-40-5 0-26 27t-29 37q-11-1-17-1t-17 1q-7-11-29-37t-25-27q-1 0-69 40-3 2-3 4 0 14 29 77-10 14-17 29-83 8-83 17v78q0 9 83 18 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 1 17 1t17-1q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-9 83-18z m0 572v-78q0-9-83-18-6-15-16-29 28-63 28-77 0-2-2-4-68-39-69-39-5 0-26 26t-29 38q-11-1-17-1t-17 1q-7-12-29-38t-25-26q-1 0-69 39-3 2-3 4 0 14 29 77-10 14-17 29-83 9-83 18v78q0 9 83 17 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 2 17 2t17-2q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-8 83-17z" horiz-adv-x="1071.4" />
+<glyph glyph-name="database" unicode="&#x26c3;" d="m375-88c-207 0-375 56-375 126 0 37 0 79 0 125 0 10 5 21 13 31 42-54 187-94 362-94s320 40 362 94c8-10 13-21 13-31 0-37 0-75 0-125 0-70-168-126-375-126z m0 251c-207 0-375 56-375 125 0 37 0 78 0 125 0 6 3 13 6 19l0 0c2 4 4 8 7 12 42-54 187-94 362-94s320 40 362 94c3-4 5-8 7-12l0 0c4-6 6-13 6-19 0-37 0-75 0-125 0-69-168-125-375-125z m0 250c-207 0-375 56-375 125 0 19 0 40 0 62 0 20 0 41 0 63 0 69 168 125 375 125 207 0 375-56 375-125 0-20 0-41 0-63 0-19 0-39 0-62 0-69-168-125-375-125z m0 312c-138 0-250-28-250-62s112-63 250-63 250 28 250 63-112 62-250 62z" horiz-adv-x="750" />
+<glyph glyph-name="gear" unicode="&#x26ed;" d="m437 508c-87 0-158-71-158-158 0-87 71-158 158-158 88 0 158 71 158 158 0 87-70 158-158 158z m318-249l-29-68 51-100 7-14-71-70-116 55-68-29-35-106-5-15h-99l-43 121-69 28-100-50-13-7-71 70 55 116-28 69-107 35-14 4v100l121 43 28 68-51 100-7 14 71 70 116-55 68 29 35 106 5 15h99l43-121 69-28 100 50 13 7 71-70-55-116 28-69 107-34 14-5v-99l-120-44z" horiz-adv-x="875" />
+<glyph glyph-name="plus" unicode="&#x2795;" d="m786 439v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q22 0 38-16t16-38z" horiz-adv-x="785.7" />
+<glyph glyph-name="minus" unicode="&#x2796;" d="m786 439v-107q0-22-16-38t-38-15h-678q-23 0-38 15t-16 38v107q0 23 16 38t38 16h678q22 0 38-16t16-38z" horiz-adv-x="785.7" />
+<glyph glyph-name="resize-vertical" unicode="&#x2b65;" d="m393 671q0-14-11-25t-25-10h-71v-572h71q15 0 25-10t11-25-11-26l-143-142q-10-11-25-11t-25 11l-143 142q-10 11-10 26t10 25 25 10h72v572h-72q-14 0-25 10t-10 25 10 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26z" horiz-adv-x="428.6" />
+<glyph glyph-name="move-up" unicode="&#x2b71;" d="m0 163h188v-313h250v313h187l-312 375-313-375z m0 687v-187h625v187h-625z" horiz-adv-x="625" />
+<glyph glyph-name="move-down" unicode="&#x2b73;" d="m625 538h-187v312h-250v-312h-188l313-375 312 375z m-625-688h625v188h-625v-188z" horiz-adv-x="625" />
 <glyph glyph-name="file-submodule" unicode="&#xe801;" d="m813 350c-32 0-188 0-188 0 0 31-31 63-62 63s-94 0-125 0-63-32-63-63 0-312 0-312h500s0 218 0 250-31 62-62 62z m-250-62h-125s0 16 0 31 14 31 31 31 47 0 62 0 32-15 32-31 0-31 0-31z m250 312c-32 0-329 0-344 0s-31 17-31 31 0 0 0 32-32 62-63 62-281 0-312 0-63-31-63-62 0-625 0-625h313s0 343 0 375 31 62 62 62 219 0 250 0 63-31 63-62h187s0 93 0 125-31 62-62 62z m-438 0h-312s0 16 0 31 15 32 31 32 234 0 250 0 31-17 31-32 0-31 0-31z" horiz-adv-x="875" />
 <glyph glyph-name="git-merge" unicode="&#xe802;" d="m625 413c-46 0-86-26-108-64-6 1-11 1-17 1-128 0-249 98-294 218 27 23 44 57 44 95 0 69-56 125-125 125s-125-56-125-125c0-47 25-86 63-108v-410c-38-21-63-61-63-107 0-70 56-126 125-126s125 56 125 126c0 46-25 86-62 107v225c82-87 195-145 312-145 6 0 11 0 17 1 22-38 62-63 108-63 69 0 125 56 125 125 0 69-56 125-125 125z m-500-438c-34 0-62 28-62 63 0 34 28 62 62 62 35 0 63-28 63-62 0-35-28-63-63-63z m0 625c-34 0-62 28-62 63s28 62 62 62c35 0 63-28 63-62s-28-63-63-63z m500-375c-34 0-62 28-62 63 0 34 28 62 62 62 35 0 63-28 63-62 0-35-28-63-63-63z" horiz-adv-x="750" />
-<glyph glyph-name="move-down" unicode="&#xe803;" d="m625 538h-187v312h-250v-312h-188l313-375 312 375z m-625-688h625v188h-625v-188z" horiz-adv-x="625" />
-<glyph glyph-name="move-up" unicode="&#xe804;" d="m0 163h188v-313h250v313h187l-312 375-313-375z m0 687v-187h625v187h-625z" horiz-adv-x="625" />
-<glyph glyph-name="diff-modified" unicode="&#xe805;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-312 406c-70 0-125-56-125-125s55-125 125-125 125 56 125 125-56 125-125 125z" horiz-adv-x="875" />
-<glyph glyph-name="diff-added" unicode="&#xe806;" d="m500 538h-125v-125h-125v-125h125v-125h125v125h125v125h-125v125z m313 250c-32 0-719 0-750 0s-63-32-63-63 0-719 0-750 31-63 63-63 718 0 750 0 62 32 62 63 0 719 0 750-31 63-62 63z m-63-719c0-16-17-31-31-31s-545 0-563 0-31 11-31 31c0 15 0 547 0 562s16 32 31 32 548 0 563 0 31-16 31-32 0-547 0-562z" horiz-adv-x="875" />
-<glyph glyph-name="diff-removed" unicode="&#xe807;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-500 219h375v125h-375v-125z" horiz-adv-x="875" />
-<glyph glyph-name="tools" unicode="&#xe808;" d="m280 396c16-16 80-83 80-83l35 36-55 57 105 112c0 0-47 47-26 28 20 74 1 157-55 215-56 58-135 77-206 57l120-125-31-122-119-33-120 125c-20-74-1-156 55-214 58-60 143-78 217-53z m402-121l-145-144 240-249c20-20 45-31 71-31 26 0 52 11 71 31 40 40 40 106 0 147l-237 246z m318 417l-153 158-451-466 55-57-270-279-62-33-87-142 23-23 137 90 32 64 270 279 55-57 451 466z" horiz-adv-x="1000" />
-<glyph glyph-name="cancel-circled" unicode="&#xe809;" d="m641 224q0 14-10 25l-101 101 101 101q10 11 10 25 0 15-10 26l-51 50q-10 11-25 11-15 0-25-11l-101-101-101 101q-11 11-26 11-15 0-25-11l-50-50q-11-11-11-26 0-14 11-25l101-101-101-101q-11-11-11-25 0-15 11-26l50-50q10-11 25-11 15 0 26 11l101 101 101-101q10-11 25-11 15 0 25 11l51 50q10 11 10 26z m216 126q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="ok-circled" unicode="&#xe80a;" d="m717 440q0 16-11 26l-50 50q-11 11-25 11t-26-11l-227-227-126 126q-11 11-25 11t-26-11l-50-50q-10-10-10-26 0-15 10-25l202-202q10-10 25-10 15 0 25 10l303 303q11 10 11 25z m140-90q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="comment" unicode="&#xe80b;" d="m750 725h-625c-60 0-125-62-125-125v-375c0-125 125-125 125-125h63v-250l250 250c0 0 252 0 312 0s125 66 125 125v375c0 61-62 125-125 125z" horiz-adv-x="875" />
-<glyph glyph-name="comment-discussion" unicode="&#xe80c;" d="m250 350c0 63 0 188 0 188s-156 0-187 0-63-32-63-63 0-281 0-312 31-63 63-63 62 0 62 0v-188l190 188s158 0 187 0 61 31 61 63 0 62 0 62-125 0-188 0-125 63-125 125z m563 375c-32 0-407 0-438 0s-62-31-62-62 0-282 0-313 31-62 62-62 186 0 186 0l189-188v188s31 0 63 0 62 31 62 62 0 281 0 313-31 62-62 62z" horiz-adv-x="875" />
-<glyph glyph-name="triangle-down" unicode="&#xe80d;" d="m0 475l375-375 375 375h-750z" horiz-adv-x="749.5" />
-<glyph glyph-name="triangle-left" unicode="&#xe80e;" d="m0 350l375-375v750l-375-375z" horiz-adv-x="374.8" />
-<glyph glyph-name="triangle-right" unicode="&#xe80f;" d="m0 725l375-375-375-375v750z" horiz-adv-x="374.9" />
-<glyph glyph-name="triangle-up" unicode="&#xe810;" d="m375 600l-375-375h750l-375 375z" horiz-adv-x="749.5" />
 <glyph glyph-name="ruler" unicode="&#xe811;" d="m125 267h42v41h-42v-41z m42 416h-42v-41h42v41z m0-750h-42v-41h-83v-42h208v42h-83v41z m-42 584h42v41h-42v-41z m0-125h42v41h-42v-41z m250 458v-1000h542v1000h-542z m333-917h-250v834h250v-834z m125 0h-83v84h83v-84z m0 125h-83v84h83v-84z m0 125h-83v84h83v-84z m0 125h-83v84h83v-84z m0 125h-83v84h83v-84z m0 125h-83v84h83v-84z m0 125h-83v84h83v-84z m-791 125h83v-41h42v41h83v42h-208v-42z m83-791h42v41h-42v-41z m0 125h42v41h-42v-41z" horiz-adv-x="1000" />
-<glyph glyph-name="globe" unicode="&#xe812;" d="m500 725c-207 0-375-168-375-375s168-375 375-375c25 0 50 2 74 7-10 5-11 40-1 59 11 22 44 78 11 97s-24 27-44 48-12 25-13 31c-5 19 19 47 20 50s1 14 1 17-15 13-19 13-5-6-10-6-28 13-32 17-7 12-14 19-7 1-18 5-43 17-68 27-28 24-28 35-15 25-22 35c-7 11-9 25-11 22s13-42 10-43-8 11-15 20 8 5-16 51 8 70 9 94 20-9 10 6 1 48-6 60-49-14-49-14c1 12 36 31 62 49s41 4 62-2 22-5 15 2 3 10 19 7 20-22 45-20 2-5 6-11-4-6-20-17 0-10 29-31 20 14 17 29 21 3 21 3c17-11 14 0 27-4s47-34 47-34c-43-24-16-26-8-32s-16-16-16-16c-9 9-10 0-16-3s0-12 0-12c-31-5-24-37-23-45s-20-19-25-30 13-35 4-36-19 36-71 22c-15-4-49-22-31-58s49 10 59 5-3-28-1-29 29-1 31-32 40-29 49-29 36 23 40 24 20 14 56-6 53-17 65-25 3-26 15-31 56 2 68-17-47-112-65-123-27-33-45-48-44-34-68-48c-22-13-26-36-35-43 168 37 293 187 293 366 0 207-168 375-375 375z m88-352c-5-1-16-11-42 5s-44 12-46 15c0 0-2 6 9 7 24 2 53-22 60-22s9 6 21 3c12-4 3-6-2-8z m-123 315c-2 2 2 4 5 7 2 3 1 6 3 8 5 6 32 13 27-2-5-15-31-16-35-13z m66-48c-9 0-31 3-27 7 16 15-6 19-19 21s-19 8-12 9 33-1 37-4 29-14 30-20 0-13-9-13z m79 3c-7-6-44 21-51 27-31 26-47 17-54 22-6 4-4 10 6 19s38-3 54-5 35-14 35-29c0-15 18-28 10-34z" horiz-adv-x="1000" />
 <glyph glyph-name="download-cloud" unicode="&#xe813;" d="m714 332q0 8-5 13t-13 5h-125v196q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-196h-125q-8 0-13-5t-5-13q0-8 5-13l196-196q5-5 13-5t13 5l196 196q5 6 5 13z m357-125q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 72 39 134t105 92q-1 17-1 24 0 118 84 202t202 84q87 0 159-49t105-129q40 35 93 35 59 0 101-42t42-101q0-43-23-77 72-17 119-76t46-133z" horiz-adv-x="1071.4" />
 <glyph glyph-name="upload-cloud" unicode="&#xe814;" d="m714 368q0 8-5 13l-196 196q-5 5-13 5t-13-5l-196-196q-5-6-5-13 0-8 5-13t13-5h125v-196q0-8 5-13t12-5h108q7 0 12 5t5 13v196h125q8 0 13 5t5 13z m357-161q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 72 39 134t105 92q-1 17-1 24 0 118 84 202t202 84q87 0 159-49t105-129q40 35 93 35 59 0 101-42t42-101q0-43-23-77 72-17 119-76t46-133z" horiz-adv-x="1071.4" />
 <glyph glyph-name="graph" unicode="&#xe815;" d="m688 600h-188v-625h188v625z m250-187h-188v-438h188v438z m-875-501v126h62v62h-62v125h62v63h-62v125h62v62h-62v125h62v63h-62v125h62v62h-125v-1000h1000v62h-937z m375 376h-188v-313h188v313z" horiz-adv-x="1000" />
 <glyph glyph-name="file-zip" unicode="&#xe816;" d="m313 288v62h-63v-62h63z m0 125v62h-63v-62h63z m0 125v62h-63v-62h63z m-125-63h62v63h-62v-63z m375 313h-563v-876h750v688l-187 188z m125-813h-625v750h187v-62h63v62h187l188-187v-563z m-500 625h62v63h-62v-63z m0-250h62v63h-62v-63z m0-125l-63-62v-125h250v125l-62 62h-63v63h-62v-63z m125-62v-63h-125v63h125z" horiz-adv-x="750" />
-<glyph glyph-name="resize-vertical" unicode="&#xe817;" d="m393 671q0-14-11-25t-25-10h-71v-572h71q15 0 25-10t11-25-11-26l-143-142q-10-11-25-11t-25 11l-143 142q-10 11-10 26t10 25 25 10h72v572h-72q-14 0-25 10t-10 25 10 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26z" horiz-adv-x="428.6" />
 <glyph glyph-name="file-code" unicode="&#xe81a;" d="m281 475l-156-156 156-156 63 62-94 94 94 94-63 62z m125-62l94-94-94-94 63-62 156 156-156 156-63-62z m157 375h-563v-876h750v688l-187 188z m125-813h-625v750h437l188-187v-563z" horiz-adv-x="750" />
-<glyph glyph-name="clippy" unicode="&#xe81b;" d="m688-25h-625v563h625v-188h62v313c0 34-28 62-62 62h-188c0 69-56 125-125 125s-125-56-125-125h-187c-35 0-63-28-63-62v-688c0-34 28-63 63-63h625c34 0 62 29 62 63v125h-62v-125z m-500 688c28 0 28 0 62 0s63 28 63 62 28 63 62 63 63-29 63-63 31-62 62-62 32 0 63 0 62-29 62-63h-500c0 38 27 63 63 63z m-63-500h125v62h-125v-62z m438 125v125l-250-188 250-187v125h312v125h-312z m-438-250h188v62h-188v-62z m313 437h-313v-62h313v62z m-188-125h-125v-62h125v62z" horiz-adv-x="875" />
 <glyph glyph-name="doc-text-inv" unicode="&#xe81c;" d="m819 584q8-7 16-20h-264v264q13-8 21-16z m-265-91h303v-589q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h446v-304q0-22 16-38t38-15z m89-411v36q0 8-5 13t-13 5h-393q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h393q8 0 13 5t5 13z m0 143v36q0 7-5 12t-13 5h-393q-8 0-13-5t-5-12v-36q0-8 5-13t13-5h393q8 0 13 5t5 13z m0 143v35q0 8-5 13t-13 5h-393q-8 0-13-5t-5-13v-35q0-8 5-13t13-5h393q8 0 13 5t5 13z" horiz-adv-x="857.1" />
 <glyph glyph-name="diff" unicode="&#xe81d;" d="m438 600h-125v-125h-125v-125h125v-125h125v125h125v125h-125v125z m-250-625h375v125h-375v-125z m437 875h-500v-62h469l219-219v-594h62v625l-250 250z m-625-125v-875h750v688l-187 187h-563z m688-813h-625v751h468l157-157v-594z" horiz-adv-x="875" />
 <glyph glyph-name="diff-ignored" unicode="&#xe81e;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-500 189v-95h96l279 279v96h-96l-279-280z" horiz-adv-x="875" />
 <glyph glyph-name="diff-renamed" unicode="&#xe81f;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-312 344h-188v-125h188v-125l250 187-250 188v-125z" horiz-adv-x="875" />
 <glyph glyph-name="paste" unicode="&#xe820;" d="m429-79h500v358h-233q-22 0-38 15t-15 38v232h-214v-643z m142 804v36q0 7-5 12t-12 6h-393q-7 0-13-6t-5-12v-36q0-7 5-13t13-5h393q7 0 12 5t5 13z m143-375h167l-167 167v-167z m286-71v-375q0-23-16-38t-38-16h-535q-23 0-38 16t-16 38v89h-303q-23 0-38 16t-16 37v750q0 23 16 38t38 16h607q22 0 38-16t15-38v-183q12-7 20-15l228-228q16-16 27-42t11-50z" horiz-adv-x="1000" />
-<glyph glyph-name="code" unicode="&#xe821;" d="m594 663l-94-94 219-219-219-219 94-93 281 312-281 313z m-313 0l-281-313 281-312 94 93-219 219 219 219-94 94z" horiz-adv-x="875" />
-<glyph glyph-name="keyhole-circled" unicode="&#xe822;" d="m500 734c-212 0-384-172-384-384 0-212 172-384 384-384 212 0 384 172 384 384 0 212-172 384-384 384z m131-646h-262l57 285c-34 24-57 63-57 108 0 72 59 131 131 131 72 0 131-59 131-131 0-45-23-84-57-108l57-285z" horiz-adv-x="1000" />
 <glyph glyph-name="file-directory" unicode="&#xe823;" d="m813 663c-32 0-329 0-344 0s-31 15-31 31 0 0 0 31-32 63-63 63-281 0-312 0-63-32-63-63 0-687 0-687h875s0 531 0 562-31 63-62 63z m-438 0h-312s0 14 0 31 15 31 31 31 235 0 250 0 31-15 31-31 0-31 0-31z" horiz-adv-x="875" />
 <glyph glyph-name="git-compare" unicode="&#xe824;" d="m813 145s0 299 0 393-94 187-188 187c-62 0-62 0-62 0v125l-188-187 188-188v125s31 0 62 0 63-31 63-62 0-393 0-393c-38-22-63-62-63-107 0-70 56-126 125-126s125 56 125 126c0 45-25 85-62 107z m-63-170c-34 0-62 28-62 63s28 62 62 62 63-28 63-62-29-63-63-63z m-437 125s-32 0-63 0-62 31-62 63 0 392 0 392c37 22 62 62 62 108 0 69-56 125-125 125s-125-56-125-125c0-46 25-86 63-108 0 0 0-299 0-392s93-188 187-188c63 0 63 0 63 0v-125l187 188-187 187v-125z m-188 500c-34 0-62 28-62 63s28 62 62 62 63-28 63-62-29-63-63-63z" horiz-adv-x="875" />
 <glyph glyph-name="git-pull-request" unicode="&#xe825;" d="m688 145s0 299 0 393-94 187-188 187c-62 0-62 0-62 0v125l-188-187 188-188v125s31 0 62 0 63-31 63-62 0-393 0-393c-38-22-63-62-63-107 0-70 56-126 125-126s125 56 125 126c0 45-25 85-62 107z m-63-170c-34 0-62 28-62 63s28 62 62 62 63-28 63-62-29-63-63-63z m-500 813c-69 0-125-56-125-125 0-46 25-86 63-108v-409c-38-22-63-62-63-107 0-70 56-126 125-126s125 56 125 126c0 45-25 85-62 107v409c37 22 62 62 62 108 0 69-56 125-125 125z m0-813c-34 0-62 28-62 63s28 62 62 62 63-28 63-62-29-63-63-63z m0 625c-34 0-62 28-62 63s28 62 62 62 63-28 63-62-29-63-63-63z" horiz-adv-x="750" />
-<glyph glyph-name="key" unicode="&#xe826;" d="m626 788c-138 0-250-112-250-250 0-19 2-38 6-56l-382-382v-62l63-63h125l62 63v62h63v63h62v62h125l69 69c18-4 37-6 57-6 138 0 250 112 250 250s-112 250-250 250z m-251-438l-312-312v62l312 313v-63z m313 188c-35 0-63 28-63 62 0 35 28 63 63 63s62-28 62-63c0-34-28-62-62-62z" horiz-adv-x="875.9" />
 <glyph glyph-name="repo-forked" unicode="&#xe827;" d="m750 725c0 69-56 125-125 125s-125-56-125-125c0-46 25-87 63-108v-104l-188-207-187 207v104c37 21 62 61 62 108 0 69-56 125-125 125s-125-56-125-125c0-46 25-87 63-108v-153l250-275v-107c-38-21-63-61-63-108 0-69 56-125 125-125s125 56 125 125c0 46-25 87-62 108v107l250 275v153c37 21 62 61 62 108z m-625 62c33 0 61-28 61-61s-28-61-61-61-60 28-60 61 27 61 60 61z m250-871c-33 0-60 28-60 61s27 61 60 61 61-28 61-61-28-61-61-61z m250 871c33 0 61-28 61-61s-28-61-61-61-60 28-60 61 27 61 60 61z" horiz-adv-x="750" />
 <glyph glyph-name="fork" unicode="&#xe828;" d="m161 29q0 22-16 38t-38 15-38-15-15-38 15-38 38-16 38 16 16 38z m0 642q0 23-16 38t-38 16-38-16-15-38 15-38 38-15 38 15 16 38z m357-71q0 22-16 38t-38 16-38-16-15-38 15-38 38-16 38 16 16 38z m53 0q0-29-14-54t-39-39q-1-160-126-231-38-21-114-45-71-22-94-39t-23-56v-15q24-14 39-39t14-53q0-45-31-76t-76-32-76 32-31 76q0 29 15 53t39 39v458q-25 14-39 39t-15 53q0 45 31 76t76 32 76-32 31-76q0-29-14-53t-39-39v-278q30 15 86 32 30 10 49 17t39 17 33 22 22 29 16 38 5 51q-25 14-39 39t-15 54q0 45 31 76t76 31 76-31 31-76z" horiz-adv-x="571.4" />
-<glyph glyph-name="trashcan" unicode="&#xe829;" d="m688 725h-250c0 0 0 24 0 31 0 18-14 32-32 32s-31-14-31-32c0-17 0-31 0-31h-250c-34 0-62-28-62-62v-63c0-34 28-62 62-62v-563c0-35 28-63 63-63h437c35 0 63 28 63 63v563c34 0 62 28 62 62v63c0 34-28 62-62 62z m-63-719c0-17-14-31-31-31h-375c-17 0-31 14-31 31v532h62v-469c0-17 14-31 31-31s32 14 32 31l0 469h62v-469c0-17 14-31 31-31s32 14 32 31l0 469h62l0-469c0-17 14-31 31-31s32 14 32 31v469h62v-532z m63 610c0-9-7-16-16-16h-531c-9 0-16 7-16 16v31c0 9 7 16 16 16h531c9 0 16-7 16-16v-31z" horiz-adv-x="750" />
-<glyph glyph-name="cancel" unicode="&#xe82a;" d="m724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
-<glyph glyph-name="search" unicode="&#xe82b;" d="m938 38l-244 243c35 57 56 123 56 194 0 207-168 375-375 375-207 0-375-168-375-375 0-207 168-375 375-375 71 0 138 21 194 56l244-244c17-17 45-17 62 0l63 63c17 17 17 45 0 63z m-563 187c-138 0-250 112-250 250s112 250 250 250 250-112 250-250-112-250-250-250z" horiz-adv-x="950.3" />
-<glyph glyph-name="heart" unicode="&#xe82c;" d="m500-79q-14 0-25 10l-348 336q-5 5-15 15t-31 36-38 55-30 67-13 77q0 123 71 192t196 70q34 0 70-12t67-33 54-38 42-38q20 20 42 38t54 38 67 33 70 12q125 0 196-70t71-192q0-123-128-251l-347-335q-10-10-25-10z" horiz-adv-x="1000" />
-<glyph glyph-name="heart-empty" unicode="&#xe82d;" d="m929 517q0 46-12 80t-31 55-46 33-52 18-55 4-62-14-62-36-48-40-34-34q-10-13-27-13t-27 13q-14 15-34 34t-48 40-62 36-62 14-55-4-52-18-46-33-31-55-12-80q0-93 105-198l324-312 324 312q105 105 105 198z m71 0q0-123-128-251l-347-335q-10-10-25-10t-25 10l-348 336q-5 5-15 15t-31 36-38 55-30 67-13 77q0 123 71 192t196 70q34 0 70-12t67-33 54-38 42-38q20 20 42 38t54 38 67 33 70 12q125 0 196-70t71-192z" horiz-adv-x="1000" />
-<glyph glyph-name="user" unicode="&#xe82e;" d="m786 66q0-67-41-106t-108-39h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q5 0 24-12t41-27 60-27 75-12 74 12 61 27 41 27 24 12q34 0 62-11t48-30 34-45 24-55 15-60 8-61 2-58z m-179 498q0-88-63-151t-151-63-152 63-62 151 62 152 152 63 151-63 63-152z" horiz-adv-x="785.7" />
-<glyph glyph-name="users" unicode="&#xe82f;" d="m331 350q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-356q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 23 12q35 0 63-11t47-30 35-45 24-54 15-61 8-61 2-58z m-572 713q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
-<glyph glyph-name="ok" unicode="&#xe830;" d="m932 534q0-22-15-38l-404-404-76-76q-16-15-38-15t-38 15l-76 76-202 202q-15 16-15 38t15 38l76 76q16 16 38 16t38-16l164-165 366 367q16 16 38 16t38-16l76-76q15-16 15-38z" horiz-adv-x="1000" />
-<glyph glyph-name="gear" unicode="&#xe831;" d="m437 508c-87 0-158-71-158-158 0-87 71-158 158-158 88 0 158 71 158 158 0 87-70 158-158 158z m318-249l-29-68 51-100 7-14-71-70-116 55-68-29-35-106-5-15h-99l-43 121-69 28-100-50-13-7-71 70 55 116-28 69-107 35-14 4v100l121 43 28 68-51 100-7 14 71 70 116-55 68 29 35 106 5 15h99l43-121 69-28 100 50 13 7 71-70-55-116 28-69 107-34 14-5v-99l-120-44z" horiz-adv-x="875" />
-<glyph glyph-name="cog-alt" unicode="&#xe832;" d="m500 350q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m429-286q0 29-22 51t-50 21-50-21-21-51q0-29 21-50t50-21 51 21 21 50z m0 572q0 29-22 50t-50 21-50-21-21-50q0-30 21-51t50-21 51 21 21 51z m-215-235v-103q0-6-4-11t-9-6l-86-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-13-17-46-50t-44-33q-6 0-11 4l-64 50q-21-11-43-17-6-60-13-87-4-13-17-13h-104q-6 0-11 4t-5 10l-13 85q-19 6-42 18l-66-50q-4-4-11-4-6 0-12 4-80 75-80 90 0 5 4 10 5 8 23 30t26 34q-13 24-20 46l-85 13q-5 1-9 5t-4 11v103q0 6 4 11t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 11 12 17 46 50t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 41-17l66 49q5 4 11 4 7 0 12-4 81-75 81-90 0-5-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q5-2 9-6t4-11z m357-298v-78q0-9-83-17-6-15-16-29 28-63 28-77 0-2-2-4-68-40-69-40-5 0-26 27t-29 37q-11-1-17-1t-17 1q-7-11-29-37t-25-27q-1 0-69 40-3 2-3 4 0 14 29 77-10 14-17 29-83 8-83 17v78q0 9 83 18 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 1 17 1t17-1q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-9 83-18z m0 572v-78q0-9-83-18-6-15-16-29 28-63 28-77 0-2-2-4-68-39-69-39-5 0-26 26t-29 38q-11-1-17-1t-17 1q-7-12-29-38t-25-26q-1 0-69 39-3 2-3 4 0 14 29 77-10 14-17 29-83 9-83 18v78q0 9 83 17 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 2 17 2t17-2q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-8 83-17z" horiz-adv-x="1071.4" />
-<glyph glyph-name="doc" unicode="&#xe833;" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z" horiz-adv-x="857.1" />
 <glyph glyph-name="docs" unicode="&#xe834;" d="m946 636q23 0 38-16t16-38v-678q0-23-16-38t-38-16h-535q-23 0-38 16t-16 38v160h-303q-23 0-38 16t-16 38v375q0 22 11 49t27 42l228 228q15 16 42 27t49 11h232q23 0 38-16t16-38v-183q38 23 71 23h232z m-303-119l-167-167h167v167z m-357 214l-167-167h167v167z m109-361l176 176v233h-214v-233q0-22-15-38t-38-15h-233v-357h286v143q0 22 11 49t27 42z m534-449v643h-215v-232q0-22-15-38t-38-15h-232v-358h500z" horiz-adv-x="1000" />
-<glyph glyph-name="doc-text" unicode="&#xe835;" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-572 483q0 7 5 12t13 5h393q8 0 13-5t5-12v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36z m411-125q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z m0-143q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z" horiz-adv-x="857.1" />
 <glyph glyph-name="doc-inv" unicode="&#xe836;" d="m571 564v264q13-8 21-16l227-228q8-7 16-20h-264z m-71-18q0-22 16-38t38-15h303v-589q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h446v-304z" horiz-adv-x="857.1" />
 <glyph glyph-name="file-powerpoint" unicode="&#xe837;" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-554 131v-59h183v59h-52v93h76q43 0 66 9 37 12 59 48t23 82q0 45-21 78t-56 49q-27 10-72 10h-206v-59h52v-310h-52z m197 156h-66v150h67q29 0 46-10 31-19 31-64 0-50-34-67-18-9-44-9z" horiz-adv-x="857.1" />
-<glyph glyph-name="arrows-cw" unicode="&#xe838;" d="m843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-37 90-57t105-20q74 0 139 37t104 99q6 10 29 66 5 13 17 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" />
-<glyph glyph-name="clock" unicode="&#xe839;" d="m500 546v-250q0-7-5-12t-13-5h-178q-8 0-13 5t-5 12v36q0 8 5 13t13 5h125v196q0 8 5 13t12 5h36q8 0 13-5t5-13z m232-196q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="lock" unicode="&#xe83a;" d="m179 421h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
-<glyph glyph-name="lock-open-alt" unicode="&#xe83b;" d="m589 421q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-15-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
-<glyph glyph-name="tag" unicode="&#xe83c;" d="m250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z" horiz-adv-x="857.1" />
 <glyph glyph-name="tags" unicode="&#xe83d;" d="m250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z m215 0q0-30-21-51l-274-274q-22-21-51-21-20 0-33 8t-29 25l262 262q21 21 21 51 0 29-21 50l-399 399q-21 21-57 36t-65 15h125q29 0 65-15t57-36l399-399q21-21 21-50z" horiz-adv-x="1071.4" />
-<glyph glyph-name="book" unicode="&#xe83e;" d="m915 583q22-32 10-72l-154-505q-10-36-42-60t-69-25h-515q-43 0-83 30t-55 74q-14 37-1 71 0 2 1 15t3 20q0 5-2 12t-2 11q1 6 5 12t9 13 9 13q13 21 25 51t17 51q2 6 0 17t0 16q2 6 9 15t10 13q12 20 23 51t14 51q1 5-1 17t0 16q2 7 12 17t13 13q10 14 23 47t16 54q0 4-2 14t-1 15q1 4 5 10t10 13 10 11q4 7 9 17t8 20 9 20 11 18 15 13 20 6 26-3l0-1q21 5 28 5h425q41 0 63-32t10-72l-152-506q-20-66-40-85t-72-20h-485q-15 0-21-8-6-9-1-24 14-39 81-39h515q16 0 31 9t19 23l168 550q4 13 3 32 21-8 33-24z m-594-1q-2-7 1-12t11-6h339q8 0 15 6t9 12l12 36q2 7-2 12t-11 6h-339q-7 0-14-6t-9-12z m-46-143q-3-7 1-12t11-6h339q7 0 14 6t10 12l11 36q3 7-1 13t-11 5h-339q-8 0-14-5t-10-13z" horiz-adv-x="928.6" />
 <glyph glyph-name="bookmark-empty" unicode="&#xe83f;" d="m643 707h-572v-693l237 227 49 47 50-47 236-227v693z m7 72q12 0 24-5 19-8 29-23t11-35v-719q0-19-11-35t-29-23q-10-4-24-4-27 0-47 18l-246 236-246-236q-20-19-46-19-13 0-25 5-18 7-29 23t-11 35v719q0 19 11 35t29 23q12 5 25 5h585z" horiz-adv-x="714.3" />
 <glyph glyph-name="bookmark" unicode="&#xe840;" d="m650 779q12 0 24-5 19-8 29-23t11-35v-719q0-19-11-35t-29-23q-10-4-24-4-27 0-47 18l-246 236-246-236q-20-19-46-19-13 0-25 5-18 7-29 23t-11 35v719q0 19 11 35t29 23q12 5 25 5h585z" horiz-adv-x="714.3" />
 <glyph glyph-name="align-left" unicode="&#xe841;" d="m1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m-214 214v-71q0-15-11-25t-25-11h-714q-15 0-25 11t-11 25v71q0 15 11 25t25 11h714q15 0 25-11t11-25z m143 215v-72q0-14-11-25t-25-11h-857q-15 0-25 11t-11 25v72q0 14 11 25t25 10h857q14 0 25-10t11-25z m-215 214v-72q0-14-10-25t-25-10h-643q-15 0-25 10t-11 25v72q0 14 11 25t25 11h643q14 0 25-11t10-25z" horiz-adv-x="1000" />
-<glyph glyph-name="pencil" unicode="&#xe842;" d="m203-7l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
 <glyph glyph-name="sliders" unicode="&#xe843;" d="m196 64v-71h-196v71h196z m197 72q14 0 25-11t11-25v-143q0-14-11-25t-25-11h-143q-14 0-25 11t-11 25v143q0 15 11 25t25 11h143z m89 214v-71h-482v71h482z m-357 286v-72h-125v72h125z m732-572v-71h-411v71h411z m-536 643q15 0 26-10t10-26v-142q0-15-10-26t-26-10h-142q-15 0-26 10t-10 26v142q0 15 10 26t26 10h142z m358-286q14 0 25-10t10-25v-143q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v143q0 14 11 25t25 10h143z m178-71v-71h-125v71h125z m0 286v-72h-482v72h482z" horiz-adv-x="857.1" />
-<glyph glyph-name="pencil-squared" unicode="&#xe844;" d="m225 232l85-85-29-29h-31v53h-54v32z m231 217q8-7-1-16l-163-163q-9-9-16-1-8 7 1 16l163 163q9 9 16 1z m-152-385l303 304-161 161-303-304v-161h161z m339 340l51 51q16 16 16 38t-16 38l-85 85q-15 15-38 15t-38-15l-51-52z m214 214v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
-<glyph glyph-name="edit" unicode="&#xe845;" d="m496 189l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 51q16 16 38 16t38-16l85-84q16-16 16-38t-16-38z" horiz-adv-x="1000" />
-<glyph glyph-name="folder-open" unicode="&#xe846;" d="m1049 319q0-18-18-37l-187-221q-24-28-67-48t-81-20h-607q-19 0-33 7t-15 24q0 17 17 37l188 221q24 28 67 48t80 20h607q19 0 34-7t15-24z m-192 192v-90h-464q-53 0-110-26t-92-67l-188-221-2-3q0 2-1 7t0 7v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q51 0 88-37t37-88z" horiz-adv-x="1071.4" />
-<glyph glyph-name="folder-open-empty" unicode="&#xe847;" d="m994 330q0 20-30 20h-607q-22 0-48-12t-39-29l-164-203q-11-13-11-22 0-20 30-20h607q22 0 48 13t40 29l164 203q10 12 10 21z m-637 91h429v90q0 22-16 38t-38 15h-321q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-476l143 175q25 30 65 49t78 19z m708-91q0-34-25-66l-165-203q-24-30-65-49t-78-19h-607q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q51 0 88-37t37-88v-90h107q30 0 56-13t37-40q8-17 8-38z" horiz-adv-x="1071.4" />
-<glyph glyph-name="down-open" unicode="&#xe848;" d="m939 399l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l92 92q11 11 26 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" />
-<glyph glyph-name="left-open" unicode="&#xe849;" d="m653 682l-296-296 296-297q11-10 11-25t-11-25l-92-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 10 25 10t25-10l92-93q11-10 11-25t-11-25z" horiz-adv-x="714.3" />
-<glyph glyph-name="right-open" unicode="&#xe84a;" d="m618 361l-414-415q-11-10-25-10t-26 10l-92 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l92 93q11 10 26 10t25-10l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
-<glyph glyph-name="up-open" unicode="&#xe84b;" d="m939 107l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-26 10l-92 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
-<glyph glyph-name="plus-circled" unicode="&#xe84c;" d="m679 314v72q0 14-11 25t-25 10h-143v143q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-143h-143q-14 0-25-10t-10-25v-72q0-14 10-25t25-11h143v-142q0-15 11-25t25-11h71q15 0 25 11t11 25v142h143q14 0 25 11t11 25z m178 36q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="minus-circled" unicode="&#xe84d;" d="m679 314v72q0 14-11 25t-25 10h-429q-14 0-25-10t-10-25v-72q0-14 10-25t25-10h429q14 0 25 10t11 25z m178 36q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="block" unicode="&#xe84e;" d="m732 352q0 90-48 164l-421-420q76-50 166-50 62 0 118 25t96 65 65 97 24 119z m-557-167l421 421q-75 50-167 50-83 0-153-40t-110-112-41-152q0-91 50-167z m682 167q0-88-34-168t-91-137-137-92-166-34-167 34-137 92-91 137-34 168 34 167 91 137 137 91 167 34 166-34 137-91 91-137 34-167z" horiz-adv-x="857.1" />
-<glyph glyph-name="plus" unicode="&#xe84f;" d="m786 439v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q22 0 38-16t16-38z" horiz-adv-x="785.7" />
-<glyph glyph-name="minus" unicode="&#xe850;" d="m786 439v-107q0-22-16-38t-38-15h-678q-23 0-38 15t-16 38v107q0 23 16 38t38 16h678q22 0 38-16t16-38z" horiz-adv-x="785.7" />
 <glyph glyph-name="eye-off" unicode="&#xe851;" d="m310 105l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-12 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-92l157 281q4-26 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-86t81-102q11-19 11-39z" horiz-adv-x="1000" />
-<glyph glyph-name="eye" unicode="&#xe852;" d="m929 314q-85 132-213 197 34-58 34-125 0-104-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197 75-114 187-182t242-68 242 68 187 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-12 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m473-215q0-19-11-38-78-129-210-206t-279-77-279 77-210 206q-11 19-11 38t11 39q78 128 210 205t279 78 279-78 210-205q11-20 11-39z" horiz-adv-x="1000" />
-<glyph glyph-name="folder" unicode="&#xe853;" d="m929 511v-393q0-51-37-88t-88-37h-679q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h375q51 0 88-37t37-88z" horiz-adv-x="928.6" />
-<glyph glyph-name="folder-empty" unicode="&#xe854;" d="m857 118v393q0 22-15 38t-38 15h-393q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-536q0-22 16-38t38-16h679q22 0 38 16t15 38z m72 393v-393q0-51-37-88t-88-37h-679q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h375q51 0 88-37t37-88z" horiz-adv-x="928.6" />
 <glyph glyph-name="rss" unicode="&#xe855;" d="m214 100q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m286-69q1-15-9-26-11-12-27-12h-75q-14 0-24 9t-11 23q-12 128-103 219t-219 103q-14 1-23 11t-9 24v75q0 16 12 26 9 10 24 10h3q89-7 170-45t145-101q63-63 101-145t45-171z m286-1q1-15-10-26-10-11-26-11h-80q-14 0-25 10t-11 23q-6 120-56 228t-129 188-188 129-227 57q-14 0-24 11t-10 24v80q0 15 11 26 10 10 25 10h1q147-8 280-67t238-164q104-104 164-238t67-280z" horiz-adv-x="785.7" />
 <glyph glyph-name="rss-squared" unicode="&#xe856;" d="m286 136q0 29-21 50t-51 21-50-21-21-50 21-51 50-21 51 21 21 51z m196-53q-8 130-99 221t-221 99q-8 1-14-5t-5-13v-71q0-8 5-13t12-5q86-6 147-68t67-147q1-7 6-12t12-5h72q7 0 13 6t5 13z m214 0q-3 86-31 166t-78 145-115 114-145 78-166 31q-8 1-13-5-5-5-5-13v-71q0-7 5-12t12-6q114-4 211-62t156-155 62-211q0-8 5-13t13-5h71q7 0 13 6 6 5 5 13z m161 535v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
-<glyph glyph-name="wrench" unicode="&#xe857;" d="m214 29q0 14-10 25t-25 10-26-10-10-25 10-26 26-10 25 10 10 26z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m353 243q0-22-12-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t4-14z" horiz-adv-x="928.6" />
-<glyph glyph-name="floppy" unicode="&#xe858;" d="m214-7h429v214h-429v-214z m500 0h72v500q0 8-6 21t-11 20l-157 156q-5 6-19 12t-22 5v-232q0-22-15-38t-38-16h-322q-22 0-37 16t-16 38v232h-72v-714h72v232q0 22 16 38t37 16h465q22 0 38-16t15-38v-232z m-214 518v178q0 8-5 13t-13 5h-107q-7 0-13-5t-5-13v-178q0-8 5-13t13-5h107q7 0 13 5t5 13z m357-18v-518q0-22-15-38t-38-16h-750q-23 0-38 16t-16 38v750q0 22 16 38t38 16h517q23 0 50-12t42-26l156-157q16-15 27-42t11-49z" horiz-adv-x="857.1" />
 <glyph glyph-name="strike" unicode="&#xe859;" d="m982 350q8 0 13-5t5-13v-36q0-7-5-12t-13-5h-964q-8 0-13 5t-5 12v36q0 8 5 13t13 5h964z m-712 36q-16 19-29 44-27 54-27 105 0 101 75 173 74 71 219 71 28 0 94-11 36-7 98-27 6-21 12-66 8-68 8-102 0-10-3-25l-7-2-46 4-8 1q-28 83-58 114-49 51-117 51-64 0-102-33-37-32-37-81 0-41 37-79t156-72q38-11 96-36 33-16 53-29h-414z m282-143h230q4-22 4-51 0-62-23-119-13-30-40-58-20-19-61-45-44-27-85-37-45-12-113-12-64 0-109 13l-78 23q-32 8-40 15-5 5-5 12v8q0 60-1 87 0 17 0 38l1 20v25l57 1q8-19 17-40t12-31 7-15q20-32 45-52 24-20 59-32 33-12 73-12 36 0 78 15 43 14 68 48 26 34 26 72 0 47-45 87-19 16-77 40z" horiz-adv-x="1000" />
-<glyph glyph-name="sort" unicode="&#xe85a;" d="m571 243q0-15-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 10-11 25t11 25 25 11h500q14 0 25-11t10-25z m0 214q0-14-10-25t-25-11h-500q-15 0-25 11t-11 25 11 25l250 250q10 11 25 11t25-11l250-250q10-10 10-25z" horiz-adv-x="571.4" />
-<glyph glyph-name="exchange" unicode="&#xe85b;" d="m1000 189v-107q0-7-5-12t-13-6h-768v-107q0-7-5-12t-13-6q-6 0-13 6l-178 178q-5 5-5 13 0 8 5 13l179 178q5 5 12 5 8 0 13-5t5-13v-107h768q7 0 13-5t5-13z m0 304q0-8-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107h-768q-7 0-13 6t-5 12v107q0 8 5 13t13 5h768v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z" horiz-adv-x="1000" />
-<glyph glyph-name="circle-empty" unicode="&#xe85c;" d="m429 654q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152-41 152-110 111-152 41z m428-304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="circle" unicode="&#xe85d;" d="m857 350q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
 <glyph glyph-name="box" unicode="&#xe85e;" d="m607 386q0 14-10 25t-26 10h-142q-15 0-26-10t-10-25 10-25 26-11h142q15 0 26 11t10 25z m322 107v-536q0-14-11-25t-25-11h-786q-14 0-25 11t-11 25v536q0 14 11 25t25 11h786q14 0 25-11t11-25z m35 250v-143q0-15-10-25t-25-11h-858q-14 0-25 11t-10 25v143q0 14 10 25t25 11h858q14 0 25-11t10-25z" horiz-adv-x="1000" />
 <glyph glyph-name="right" unicode="&#xe85f;" d="m964 352q0-8-5-14l-215-197q-9-8-19-4-11 5-11 17v125h-696q-8 0-13 5t-5 12v108q0 7 5 12t13 5h696v125q0 12 11 17t19-3l215-196q5-5 5-12z" horiz-adv-x="1000" />
 <glyph glyph-name="left" unicode="&#xe860;" d="m1000 404v-108q0-7-5-12t-13-5h-696v-125q0-12-11-17t-19 3l-215 196q-5 5-5 12 0 8 5 14l215 197q9 8 19 4 11-5 11-17v-125h696q8 0 13-5t5-12z" horiz-adv-x="1000" />
@@ -107,6 +74,39 @@
 <glyph glyph-name="up" unicode="&#xe864;" d="m427 575q-5-11-16-11h-125v-696q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v696h-125q-12 0-16 11t3 19l195 215q5 5 13 5 7 0 13-5l198-215q7-9 3-19z" horiz-adv-x="428.6" />
 <glyph glyph-name="down" unicode="&#xe865;" d="m427 125q4-10-3-19l-195-215q-6-5-13-5-8 0-13 5l-198 215q-8 9-3 19 5 11 16 11h125v696q0 8 5 13t13 5h107q8 0 13-5t5-13v-696h125q11 0 16-11z" horiz-adv-x="428.6" />
 <glyph glyph-name="down-circled" unicode="&#xe866;" d="m625 332q0-7-6-13l-178-178q-6-5-12-5t-13 5l-179 178q-8 9-4 20 5 11 17 11h107v196q0 8 5 13t13 5h107q8 0 13-5t5-13v-196h107q8 0 13-5t5-13z m-196 322q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152-41 152-110 111-152 41z m428-304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="globe" unicode="&#x1f30e;" d="m500 725c-207 0-375-168-375-375s168-375 375-375c25 0 50 2 74 7-10 5-11 40-1 59 11 22 44 78 11 97s-24 27-44 48-12 25-13 31c-5 19 19 47 20 50s1 14 1 17-15 13-19 13-5-6-10-6-28 13-32 17-7 12-14 19-7 1-18 5-43 17-68 27-28 24-28 35-15 25-22 35c-7 11-9 25-11 22s13-42 10-43-8 11-15 20 8 5-16 51 8 70 9 94 20-9 10 6 1 48-6 60-49-14-49-14c1 12 36 31 62 49s41 4 62-2 22-5 15 2 3 10 19 7 20-22 45-20 2-5 6-11-4-6-20-17 0-10 29-31 20 14 17 29 21 3 21 3c17-11 14 0 27-4s47-34 47-34c-43-24-16-26-8-32s-16-16-16-16c-9 9-10 0-16-3s0-12 0-12c-31-5-24-37-23-45s-20-19-25-30 13-35 4-36-19 36-71 22c-15-4-49-22-31-58s49 10 59 5-3-28-1-29 29-1 31-32 40-29 49-29 36 23 40 24 20 14 56-6 53-17 65-25 3-26 15-31 56 2 68-17-47-112-65-123-27-33-45-48-44-34-68-48c-22-13-26-36-35-43 168 37 293 187 293 366 0 207-168 375-375 375z m88-352c-5-1-16-11-42 5s-44 12-46 15c0 0-2 6 9 7 24 2 53-22 60-22s9 6 21 3c12-4 3-6-2-8z m-123 315c-2 2 2 4 5 7 2 3 1 6 3 8 5 6 32 13 27-2-5-15-31-16-35-13z m66-48c-9 0-31 3-27 7 16 15-6 19-19 21s-19 8-12 9 33-1 37-4 29-14 30-20 0-13-9-13z m79 3c-7-6-44 21-51 27-31 26-47 17-54 22-6 4-4 10 6 19s38-3 54-5 35-14 35-29c0-15 18-28 10-34z" horiz-adv-x="1000" />
+<glyph glyph-name="eye" unicode="&#x1f441;" d="m929 314q-85 132-213 197 34-58 34-125 0-104-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197 75-114 187-182t242-68 242 68 187 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-12 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m473-215q0-19-11-38-78-129-210-206t-279-77-279 77-210 206q-11 19-11 38t11 39q78 128 210 205t279 78 279-78 210-205q11-20 11-39z" horiz-adv-x="1000" />
+<glyph glyph-name="user" unicode="&#x1f464;" d="m786 66q0-67-41-106t-108-39h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q5 0 24-12t41-27 60-27 75-12 74 12 61 27 41 27 24 12q34 0 62-11t48-30 34-45 24-55 15-60 8-61 2-58z m-179 498q0-88-63-151t-151-63-152 63-62 151 62 152 152 63 151-63 63-152z" horiz-adv-x="785.7" />
+<glyph glyph-name="users" unicode="&#x1f465;" d="m331 350q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-356q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 23 12q35 0 63-11t47-30 35-45 24-54 15-61 8-61 2-58z m-572 713q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
+<glyph glyph-name="floppy" unicode="&#x1f4be;" d="m214-7h429v214h-429v-214z m500 0h72v500q0 8-6 21t-11 20l-157 156q-5 6-19 12t-22 5v-232q0-22-15-38t-38-16h-322q-22 0-37 16t-16 38v232h-72v-714h72v232q0 22 16 38t37 16h465q22 0 38-16t15-38v-232z m-214 518v178q0 8-5 13t-13 5h-107q-7 0-13-5t-5-13v-178q0-8 5-13t13-5h107q7 0 13 5t5 13z m357-18v-518q0-22-15-38t-38-16h-750q-23 0-38 16t-16 38v750q0 22 16 38t38 16h517q23 0 50-12t42-26l156-157q16-15 27-42t11-49z" horiz-adv-x="857.1" />
+<glyph glyph-name="folder-empty" unicode="&#x1f4c1;" d="m857 118v393q0 22-15 38t-38 15h-393q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-536q0-22 16-38t38-16h679q22 0 38 16t15 38z m72 393v-393q0-51-37-88t-88-37h-679q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h375q51 0 88-37t37-88z" horiz-adv-x="928.6" />
+<glyph glyph-name="folder-open-empty" unicode="&#x1f4c2;" d="m994 330q0 20-30 20h-607q-22 0-48-12t-39-29l-164-203q-11-13-11-22 0-20 30-20h607q22 0 48 13t40 29l164 203q10 12 10 21z m-637 91h429v90q0 22-16 38t-38 15h-321q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-476l143 175q25 30 65 49t78 19z m708-91q0-34-25-66l-165-203q-24-30-65-49t-78-19h-607q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q51 0 88-37t37-88v-90h107q30 0 56-13t37-40q8-17 8-38z" horiz-adv-x="1071.4" />
+<glyph glyph-name="clippy" unicode="&#x1f4cb;" d="m688-25h-625v563h625v-188h62v313c0 34-28 62-62 62h-188c0 69-56 125-125 125s-125-56-125-125h-187c-35 0-63-28-63-62v-688c0-34 28-63 63-63h625c34 0 62 29 62 63v125h-62v-125z m-500 688c28 0 28 0 62 0s63 28 63 62 28 63 62 63 63-29 63-63 31-62 62-62 32 0 63 0 62-29 62-63h-500c0 38 27 63 63 63z m-63-500h125v62h-125v-62z m438 125v125l-250-188 250-187v125h312v125h-312z m-438-250h188v62h-188v-62z m313 437h-313v-62h313v62z m-188-125h-125v-62h125v62z" horiz-adv-x="875" />
+<glyph glyph-name="book" unicode="&#x1f4d2;" d="m915 583q22-32 10-72l-154-505q-10-36-42-60t-69-25h-515q-43 0-83 30t-55 74q-14 37-1 71 0 2 1 15t3 20q0 5-2 12t-2 11q1 6 5 12t9 13 9 13q13 21 25 51t17 51q2 6 0 17t0 16q2 6 9 15t10 13q12 20 23 51t14 51q1 5-1 17t0 16q2 7 12 17t13 13q10 14 23 47t16 54q0 4-2 14t-1 15q1 4 5 10t10 13 10 11q4 7 9 17t8 20 9 20 11 18 15 13 20 6 26-3l0-1q21 5 28 5h425q41 0 63-32t10-72l-152-506q-20-66-40-85t-72-20h-485q-15 0-21-8-6-9-1-24 14-39 81-39h515q16 0 31 9t19 23l168 550q4 13 3 32 21-8 33-24z m-594-1q-2-7 1-12t11-6h339q8 0 15 6t9 12l12 36q2 7-2 12t-11 6h-339q-7 0-14-6t-9-12z m-46-143q-3-7 1-12t11-6h339q7 0 14 6t10 12l11 36q3 7-1 13t-11 5h-339q-8 0-14-5t-10-13z" horiz-adv-x="928.6" />
+<glyph glyph-name="search" unicode="&#x1f50d;" d="m938 38l-244 243c35 57 56 123 56 194 0 207-168 375-375 375-207 0-375-168-375-375 0-207 168-375 375-375 71 0 138 21 194 56l244-244c17-17 45-17 62 0l63 63c17 17 17 45 0 63z m-563 187c-138 0-250 112-250 250s112 250 250 250 250-112 250-250-112-250-250-250z" horiz-adv-x="950.3" />
+<glyph glyph-name="keyhole-circled" unicode="&#x1f510;" d="m500 734c-212 0-384-172-384-384 0-212 172-384 384-384 212 0 384 172 384 384 0 212-172 384-384 384z m131-646h-262l57 285c-34 24-57 63-57 108 0 72 59 131 131 131 72 0 131-59 131-131 0-45-23-84-57-108l57-285z" horiz-adv-x="1000" />
+<glyph glyph-name="key" unicode="&#x1f511;" d="m626 788c-138 0-250-112-250-250 0-19 2-38 6-56l-382-382v-62l63-63h125l62 63v62h63v63h62v62h125l69 69c18-4 37-6 57-6 138 0 250 112 250 250s-112 250-250 250z m-251-438l-312-312v62l312 313v-63z m313 188c-35 0-63 28-63 62 0 35 28 63 63 63s62-28 62-63c0-34-28-62-62-62z" horiz-adv-x="875.9" />
+<glyph glyph-name="lock" unicode="&#x1f512;" d="m179 421h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
+<glyph glyph-name="lock-open-alt" unicode="&#x1f513;" d="m589 421q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-15-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
+<glyph glyph-name="tag" unicode="&#x1f516;" d="m250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z" horiz-adv-x="857.1" />
+<glyph glyph-name="wrench" unicode="&#x1f527;" d="m214 29q0 14-10 25t-25 10-26-10-10-25 10-26 26-10 25 10 10 26z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m353 243q0-22-12-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t4-14z" horiz-adv-x="928.6" />
+<glyph glyph-name="pencil" unicode="&#x1f589;" d="m203-7l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
+<glyph glyph-name="pencil-squared" unicode="&#x1f58a;" d="m225 232l85-85-29-29h-31v53h-54v32z m231 217q8-7-1-16l-163-163q-9-9-16-1-8 7 1 16l163 163q9 9 16 1z m-152-385l303 304-161 161-303-304v-161h161z m339 340l51 51q16 16 16 38t-16 38l-85 85q-15 15-38 15t-38-15l-51-52z m214 214v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
+<glyph glyph-name="edit" unicode="&#x1f58b;" d="m496 189l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 51q16 16 38 16t38-16l85-84q16-16 16-38t-16-38z" horiz-adv-x="1000" />
+<glyph glyph-name="folder" unicode="&#x1f5c0;" d="m929 511v-393q0-51-37-88t-88-37h-679q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h375q51 0 88-37t37-88z" horiz-adv-x="928.6" />
+<glyph glyph-name="folder-open" unicode="&#x1f5c1;" d="m1049 319q0-18-18-37l-187-221q-24-28-67-48t-81-20h-607q-19 0-33 7t-15 24q0 17 17 37l188 221q24 28 67 48t80 20h607q19 0 34-7t15-24z m-192 192v-90h-464q-53 0-110-26t-92-67l-188-221-2-3q0 2-1 7t0 7v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q51 0 88-37t37-88z" horiz-adv-x="1071.4" />
+<glyph glyph-name="doc" unicode="&#x1f5c5;" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z" horiz-adv-x="857.1" />
+<glyph glyph-name="doc-text" unicode="&#x1f5c8;" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-572 483q0 7 5 12t13 5h393q8 0 13-5t5-12v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36z m411-125q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z m0-143q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z" horiz-adv-x="857.1" />
+<glyph glyph-name="trashcan" unicode="&#x1f5d1;" d="m688 725h-250c0 0 0 24 0 31 0 18-14 32-32 32s-31-14-31-32c0-17 0-31 0-31h-250c-34 0-62-28-62-62v-63c0-34 28-62 62-62v-563c0-35 28-63 63-63h437c35 0 63 28 63 63v563c34 0 62 28 62 62v63c0 34-28 62-62 62z m-63-719c0-17-14-31-31-31h-375c-17 0-31 14-31 31v532h62v-469c0-17 14-31 31-31s32 14 32 31l0 469h62v-469c0-17 14-31 31-31s32 14 32 31l0 469h62l0-469c0-17 14-31 31-31s32 14 32 31v469h62v-532z m63 610c0-9-7-16-16-16h-531c-9 0-16 7-16 16v31c0 9 7 16 16 16h531c9 0 16-7 16-16v-31z" horiz-adv-x="750" />
+<glyph glyph-name="arrows-cw" unicode="&#x1f5d8;" d="m843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-37 90-57t105-20q74 0 139 37t104 99q6 10 29 66 5 13 17 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" />
+<glyph glyph-name="comment" unicode="&#x1f5e9;" d="m750 725h-625c-60 0-125-62-125-125v-375c0-125 125-125 125-125h63v-250l250 250c0 0 252 0 312 0s125 66 125 125v375c0 61-62 125-125 125z" horiz-adv-x="875" />
+<glyph glyph-name="comment-discussion" unicode="&#x1f5ea;" d="m250 350c0 63 0 188 0 188s-156 0-187 0-63-32-63-63 0-281 0-312 31-63 63-63 62 0 62 0v-188l190 188s158 0 187 0 61 31 61 63 0 62 0 62-125 0-188 0-125 63-125 125z m563 375c-32 0-407 0-438 0s-62-31-62-62 0-282 0-313 31-62 62-62 186 0 186 0l189-188v188s31 0 63 0 62 31 62 62 0 281 0 313-31 62-62 62z" horiz-adv-x="875" />
+<glyph glyph-name="cancel" unicode="&#x1f5f4;" d="m724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
+<glyph glyph-name="cancel-circled" unicode="&#x1f5f5;" d="m641 224q0 14-10 25l-101 101 101 101q10 11 10 25 0 15-10 26l-51 50q-10 11-25 11-15 0-25-11l-101-101-101 101q-11 11-26 11-15 0-25-11l-50-50q-11-11-11-26 0-14 11-25l101-101-101-101q-11-11-11-25 0-15 11-26l50-50q10-11 25-11 15 0 26 11l101 101 101-101q10-11 25-11 15 0 25 11l51 50q10 11 10 26z m216 126q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="ok" unicode="&#x1f5f8;" d="m932 534q0-22-15-38l-404-404-76-76q-16-15-38-15t-38 15l-76 76-202 202q-15 16-15 38t15 38l76 76q16 16 38 16t38-16l164-165 366 367q16 16 38 16t38-16l76-76q15-16 15-38z" horiz-adv-x="1000" />
+<glyph glyph-name="ok-circled" unicode="&#x1f5f9;" d="m717 440q0 16-11 26l-50 50q-11 11-25 11t-26-11l-227-227-126 126q-11 11-25 11t-26-11l-50-50q-10-10-10-26 0-15 10-25l202-202q10-10 25-10 15 0 25 10l303 303q11 10 11 25z m140-90q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="block" unicode="&#x1f6ab;" d="m732 352q0 90-48 164l-421-420q76-50 166-50 62 0 118 25t96 65 65 97 24 119z m-557-167l421 421q-75 50-167 50-83 0-153-40t-110-112-41-152q0-91 50-167z m682 167q0-88-34-168t-91-137-137-92-166-34-167 34-137 92-91 137-34 168 34 167 91 137 137 91 167 34 166-34 137-91 91-137 34-167z" horiz-adv-x="857.1" />
+<glyph glyph-name="tools" unicode="&#x1f6e0;" d="m280 396c16-16 80-83 80-83l35 36-55 57 105 112c0 0-47 47-26 28 20 74 1 157-55 215-56 58-135 77-206 57l120-125-31-122-119-33-120 125c-20-74-1-156 55-214 58-60 143-78 217-53z m402-121l-145-144 240-249c20-20 45-31 71-31 26 0 52 11 71 31 40 40 40 106 0 147l-237 246z m318 417l-153 158-451-466 55-57-270-279-62-33-87-142 23-23 137 90 32 64 270 279 55-57 451 466z" horiz-adv-x="1000" />
 </font>
 </defs>
 </svg>
\ No newline at end of file
Binary file kallithea/public/fontello/font/kallithea.ttf has changed
Binary file kallithea/public/fontello/font/kallithea.woff has changed
--- a/kallithea/public/js/base.js	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/public/js/base.js	Mon Jul 20 15:07:54 2015 +0200
@@ -372,7 +372,6 @@
         .fail(function(jqXHR, textStatus, errorThrown) {
                 if (textStatus == "abort")
                     return;
-                console.log('Ajax failure: ' + textStatus);
                 $target.html('<span class="error_red">ERROR: {0}</span>'.format(textStatus));
                 $target.css('opacity','1.0');
             })
@@ -692,6 +691,7 @@
             $tr.removeClass('form-open');
             $form.remove();
             _renderInlineComment(json_data);
+            linkInlineComments($('.firstlink'), $('.comment'));
         };
         var postData = {
                 'text': text,
@@ -731,7 +731,7 @@
         // callbacks
         tooltip_activate();
         MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno,
-                             _USERS_AC_DATA, _GROUPS_AC_DATA);
+                             _USERS_AC_DATA);
         $('#text_'+lineno).focus();
     },10)
 };
@@ -857,10 +857,9 @@
 /**
  * Double link comments
  */
-var linkInlineComments = function(firstlinks, comments){
-    var $comments = $(comments);
+var linkInlineComments = function($firstlinks, $comments){
     if ($comments.length > 0) {
-        $(firstlinks).html('<a href="#{0}">First comment</a>'.format($comments.attr('id')));
+        $firstlinks.html('<a href="#{0}">First comment</a>'.format($comments.attr('id')));
     }
     if ($comments.length <= 1) {
         return;
@@ -869,18 +868,17 @@
     $comments.each(function(i, e){
             var prev = '';
             if (i > 0){
-                var prev_anchor = YUD.getAttribute(comments.item(i-1),'id');
+                var prev_anchor = $($comments.get(i-1)).attr('id');
                 prev = '<a href="#{0}">Previous comment</a>'.format(prev_anchor);
             }
             var next = '';
-            if (i+1 < comments.length){
-                var next_anchor = YUD.getAttribute(comments.item(i+1),'id');
+            if (i+1 < $comments.length){
+                var next_anchor = $($comments.get(i+1)).attr('id');
                 next = '<a href="#{0}">Next comment</a>'.format(next_anchor);
             }
-            var $div = $(('<div class="prev-next-comment">'+
-                          '<div class="prev-comment">{0}</div>'+
-                          '<div class="next-comment">{1}</div>').format(prev, next));
-            $div.prependTo(this);
+            $(this).find('.comment-prev-next-links').html(
+                '<div class="prev-comment">{0}</div>'.format(prev) +
+                '<div class="next-comment">{0}</div>'.format(next));
         });
 }
 
@@ -920,7 +918,7 @@
                     }
                 })
             .fail(function() {
-                    console.log('failed to load');
+                    console.log('fileBrowserListeners initFilter failed to load');
                 })
         ;
     }
@@ -1109,332 +1107,240 @@
     ajaxPOST(sUrl, postData, success, failure);
 };
 
-/** MEMBERS AUTOCOMPLETE WIDGET **/
+/**
+ * Autocomplete functionality
+ */
+
+// Custom search function for the DataSource of users
+var autocompleteMatchUsers = function (sQuery, myUsers) {
+    // Case insensitive matching
+    var query = sQuery.toLowerCase();
+    var i = 0;
+    var l = myUsers.length;
+    var matches = [];
 
-var _MembersAutoComplete = function (divid, cont, users_list, groups_list) {
-    var myUsers = users_list;
-    var myGroups = groups_list;
+    // Match against each name of each contact
+    for (; i < l; i++) {
+        var contact = myUsers[i];
+        if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
+             ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
+             ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
+            matches[matches.length] = contact;
+        }
+    }
+    return matches;
+};
+
+// Custom search function for the DataSource of userGroups
+var autocompleteMatchGroups = function (sQuery, myGroups) {
+    // Case insensitive matching
+    var query = sQuery.toLowerCase();
+    var i = 0;
+    var l = myGroups.length;
+    var matches = [];
 
-    // Define a custom search function for the DataSource of users
-    var matchUsers = function (sQuery) {
-            // Case insensitive matching
-            var query = sQuery.toLowerCase();
-            var i = 0;
-            var l = myUsers.length;
-            var matches = [];
+    // Match against each name of each group
+    for (; i < l; i++) {
+        var matched_group = myGroups[i];
+        if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
+            matches[matches.length] = matched_group;
+        }
+    }
+    return matches;
+};
+
+// Helper highlight function for the formatter
+var autocompleteHighlightMatch = function (full, snippet, matchindex) {
+    return full.substring(0, matchindex)
+    + "<span class='match'>"
+    + full.substr(matchindex, snippet.length)
+    + "</span>" + full.substring(matchindex + snippet.length);
+};
 
-            // Match against each name of each contact
-            for (; i < l; i++) {
-                var contact = myUsers[i];
-                if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
-                     ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
-                     ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
-                    matches[matches.length] = contact;
-                }
-            }
-            return matches;
-        };
+var gravatar = function(link, size, cssclass) {
+    var elem = '<img alt="gravatar" class="{2}" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, link, cssclass);
+    if (!link) {
+        elem = '<i class="icon-user {1}" style="font-size: {0}px;"></i>'.format(size, cssclass);
+    }
+    return elem;
+}
+
+var autocompleteGravatar = function(res, link, size, group) {
+    var elem = gravatar(link, size, "perm-gravatar-ac");
+    if (group !== undefined) {
+        elem = '<i class="perm-gravatar-ac icon-users"></i>';
+    }
+    return '<div class="ac-container-wrap">{0}{1}</div>'.format(elem, res);
+}
+
+// Custom formatter to highlight the matching letters
+var autocompleteFormatter = function (oResultData, sQuery, sResultMatch) {
+    var query = sQuery.toLowerCase();
+
+    // group
+    if (oResultData.grname != undefined) {
+        var grname = oResultData.grname;
+        var grmembers = oResultData.grmembers;
+        var grnameMatchIndex = grname.toLowerCase().indexOf(query);
+        var grprefix = "{0}: ".format(_TM['Group']);
+        var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
 
-    // Define a custom search function for the DataSource of userGroups
-    var matchGroups = function (sQuery) {
-            // Case insensitive matching
-            var query = sQuery.toLowerCase();
-            var i = 0;
-            var l = myGroups.length;
-            var matches = [];
+        if (grnameMatchIndex > -1) {
+            return autocompleteGravatar(grprefix + autocompleteHighlightMatch(grname, query, grnameMatchIndex) + grsuffix, null, null, true);
+        }
+        return autocompleteGravatar(grprefix + oResultData.grname + grsuffix, null, null, true);
+
+    // users
+    } else if (oResultData.nname != undefined) {
+        var fname = oResultData.fname || "";
+        var lname = oResultData.lname || "";
+        var nname = oResultData.nname;
+
+        // Guard against null value
+        var fnameMatchIndex = fname.toLowerCase().indexOf(query),
+            lnameMatchIndex = lname.toLowerCase().indexOf(query),
+            nnameMatchIndex = nname.toLowerCase().indexOf(query),
+            displayfname, displaylname, displaynname, displayname;
 
-            // Match against each name of each contact
-            for (; i < l; i++) {
-                var matched_group = myGroups[i];
-                if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
-                    matches[matches.length] = matched_group;
-                }
-            }
-            return matches;
-        };
+        if (fnameMatchIndex > -1) {
+            displayfname = autocompleteHighlightMatch(fname, query, fnameMatchIndex);
+        } else {
+            displayfname = fname;
+        }
+
+        if (lnameMatchIndex > -1) {
+            displaylname = autocompleteHighlightMatch(lname, query, lnameMatchIndex);
+        } else {
+            displaylname = lname;
+        }
+
+        if (nnameMatchIndex > -1) {
+            displaynname = autocompleteHighlightMatch(nname, query, nnameMatchIndex);
+        } else {
+            displaynname = nname;
+        }
+
+        displayname = displaynname;
+        if (displayfname && displaylname) {
+            displayname = "{0} {1} ({2})".format(displayfname, displaylname, displayname);
+        }
 
-    //match all
-    var matchAll = function (sQuery) {
-            var u = matchUsers(sQuery);
-            var g = matchGroups(sQuery);
-            return u.concat(g);
-        };
+        return autocompleteGravatar(displayname, oResultData.gravatar_lnk, oResultData.gravatar_size);
+    } else {
+        return '';
+    }
+};
+
+// Generate a basic autocomplete instance that can be tweaked further by the caller
+var autocompleteCreate = function (inputElement, container, matchFunc) {
+    var datasource = new YAHOO.util.FunctionDataSource(matchFunc);
+
+    var autocomplete = new YAHOO.widget.AutoComplete(inputElement, container, datasource);
+    autocomplete.useShadow = false;
+    autocomplete.resultTypeList = false;
+    autocomplete.animVert = false;
+    autocomplete.animHoriz = false;
+    autocomplete.animSpeed = 0.1;
+    autocomplete.formatResult = autocompleteFormatter;
+
+    return autocomplete;
+}
+
+var SimpleUserAutoComplete = function (inputElement, container, users_list) {
 
-    // DataScheme for members
-    var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
-    memberDS.responseSchema = {
-        fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk", "gravatar_size"]
+    var matchUsers = function (sQuery) {
+        return autocompleteMatchUsers(sQuery, users_list);
+    }
+
+    var userAC = autocompleteCreate(inputElement, container, matchUsers);
+
+    // Handler for selection of an entry
+    var itemSelectHandler = function (sType, aArgs) {
+        var myAC = aArgs[0]; // reference back to the AC instance
+        var elLI = aArgs[1]; // reference to the selected LI element
+        var oData = aArgs[2]; // object literal of selected item's result data
+        myAC.getInputEl().value = oData.nname;
     };
+    userAC.itemSelectEvent.subscribe(itemSelectHandler);
+}
 
-    // DataScheme for owner
-    var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
-    ownerDS.responseSchema = {
-        fields: ["id", "fname", "lname", "nname", "gravatar_lnk", "gravatar_size"]
+var MembersAutoComplete = function (inputElement, container, users_list, groups_list) {
+
+    var matchAll = function (sQuery) {
+        var u = autocompleteMatchUsers(sQuery, users_list);
+        var g = autocompleteMatchGroups(sQuery, groups_list);
+        return u.concat(g);
     };
 
-    // Instantiate AutoComplete for perms
-    var membersAC = new YAHOO.widget.AutoComplete(divid, cont, memberDS);
-    membersAC.useShadow = false;
-    membersAC.resultTypeList = false;
-    membersAC.animVert = false;
-    membersAC.animHoriz = false;
-    membersAC.animSpeed = 0.1;
-
-    // Instantiate AutoComplete for owner
-    var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
-    ownerAC.useShadow = false;
-    ownerAC.resultTypeList = false;
-    ownerAC.animVert = false;
-    ownerAC.animHoriz = false;
-    ownerAC.animSpeed = 0.1;
-
-    // Helper highlight function for the formatter
-    var highlightMatch = function (full, snippet, matchindex) {
-            return full.substring(0, matchindex)
-            + "<span class='match'>"
-            + full.substr(matchindex, snippet.length)
-            + "</span>" + full.substring(matchindex + snippet.length);
-        };
-
-    // Custom formatter to highlight the matching letters
-    var custom_formatter = function (oResultData, sQuery, sResultMatch) {
-            var query = sQuery.toLowerCase();
-            var _gravatar = function(res, em, size, group){
-                var elem = '<img alt="gravatar" class="perm-gravatar-ac" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, em);
-                if (!em) {
-                    elem = '<i class="icon-user perm-gravatar-ac" style="font-size: {0}px;"></i>'.format(size);
-                }
-                if (group !== undefined){
-                    elem = '<i class="perm-gravatar-ac icon-users"></i>'
-                }
-                var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'
-                return tmpl.format(elem,res)
-            }
-            // group
-            if (oResultData.grname != undefined) {
-                var grname = oResultData.grname;
-                var grmembers = oResultData.grmembers;
-                var grnameMatchIndex = grname.toLowerCase().indexOf(query);
-                var grprefix = "{0}: ".format(_TM['Group']);
-                var grsuffix = " (" + grmembers + "  )";
-                var grsuffix = " ({0}  {1})".format(grmembers, _TM['members']);
-
-                if (grnameMatchIndex > -1) {
-                    return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,null,true);
-                }
-                return _gravatar(grprefix + oResultData.grname + grsuffix, null, null, true);
-            // Users
-            } else if (oResultData.nname != undefined) {
-                var fname = oResultData.fname || "";
-                var lname = oResultData.lname || "";
-                var nname = oResultData.nname;
+    var membersAC = autocompleteCreate(inputElement, container, matchAll);
 
-                // Guard against null value
-                var fnameMatchIndex = fname.toLowerCase().indexOf(query),
-                    lnameMatchIndex = lname.toLowerCase().indexOf(query),
-                    nnameMatchIndex = nname.toLowerCase().indexOf(query),
-                    displayfname, displaylname, displaynname;
-
-                if (fnameMatchIndex > -1) {
-                    displayfname = highlightMatch(fname, query, fnameMatchIndex);
-                } else {
-                    displayfname = fname;
-                }
-
-                if (lnameMatchIndex > -1) {
-                    displaylname = highlightMatch(lname, query, lnameMatchIndex);
-                } else {
-                    displaylname = lname;
-                }
-
-                if (nnameMatchIndex > -1) {
-                    displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
-                } else {
-                    displaynname = nname ? "(" + nname + ")" : "";
-                }
-
-                return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk, oResultData.gravatar_size);
-            } else {
-                return '';
-            }
-        };
-    membersAC.formatResult = custom_formatter;
-    ownerAC.formatResult = custom_formatter;
-
-    var myHandler = function (sType, aArgs) {
-            var nextId = divid.split('perm_new_member_name_')[1];
-            var myAC = aArgs[0]; // reference back to the AC instance
-            var elLI = aArgs[1]; // reference to the selected LI element
-            var oData = aArgs[2]; // object literal of selected item's result data
-            //fill the autocomplete with value
-            if (oData.nname != undefined) {
-                //users
-                myAC.getInputEl().value = oData.nname;
-                $('#perm_new_member_type_'+nextId).val('user');
-            } else {
-                //groups
-                myAC.getInputEl().value = oData.grname;
-                $('#perm_new_member_type_'+nextId).val('users_group');
-            }
-        };
-
-    membersAC.itemSelectEvent.subscribe(myHandler);
-    if(ownerAC.itemSelectEvent){
-        ownerAC.itemSelectEvent.subscribe(myHandler);
-    }
-
-    return {
-        memberDS: memberDS,
-        ownerDS: ownerDS,
-        membersAC: membersAC,
-        ownerAC: ownerAC
+    // Handler for selection of an entry
+    var itemSelectHandler = function (sType, aArgs) {
+        var nextId = inputElement.split('perm_new_member_name_')[1];
+        var myAC = aArgs[0]; // reference back to the AC instance
+        var elLI = aArgs[1]; // reference to the selected LI element
+        var oData = aArgs[2]; // object literal of selected item's result data
+        //fill the autocomplete with value
+        if (oData.nname != undefined) {
+            //users
+            myAC.getInputEl().value = oData.nname;
+            $('#perm_new_member_type_'+nextId).val('user');
+        } else {
+            //groups
+            myAC.getInputEl().value = oData.grname;
+            $('#perm_new_member_type_'+nextId).val('users_group');
+        }
     };
+    membersAC.itemSelectEvent.subscribe(itemSelectHandler);
 }
 
-var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
-    var myUsers = users_list;
-    var myGroups = groups_list;
+var MentionsAutoComplete = function (inputElement, container, users_list) {
 
-    // Define a custom search function for the DataSource of users
     var matchUsers = function (sQuery) {
             var org_sQuery = sQuery;
             if(this.mentionQuery == null){
                 return []
             }
             sQuery = this.mentionQuery;
-            // Case insensitive matching
-            var query = sQuery.toLowerCase();
-            var i = 0;
-            var l = myUsers.length;
-            var matches = [];
-
-            // Match against each name of each contact
-            for (; i < l; i++) {
-                var contact = myUsers[i];
-                if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
-                     ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
-                     ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
-                    matches[matches.length] = contact;
-                }
-            }
-            return matches
-        };
-
-    //match all
-    var matchAll = function (sQuery) {
-            return matchUsers(sQuery);
-        };
-
-    // DataScheme for owner
-    var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
-
-    ownerDS.responseSchema = {
-        fields: ["id", "fname", "lname", "nname", "gravatar_lnk", "gravatar_size"]
-    };
-
-    // Instantiate AutoComplete for mentions
-    var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
-    ownerAC.useShadow = false;
-    ownerAC.resultTypeList = false;
-    ownerAC.suppressInputUpdate = true;
-    ownerAC.animVert = false;
-    ownerAC.animHoriz = false;
-    ownerAC.animSpeed = 0.1;
-
-    // Helper highlight function for the formatter
-    var highlightMatch = function (full, snippet, matchindex) {
-            return full.substring(0, matchindex)
-                + "<span class='match'>"
-                + full.substr(matchindex, snippet.length)
-                + "</span>" + full.substring(matchindex + snippet.length);
-        };
+            return autocompleteMatchUsers(sQuery, users_list);
+    }
 
-    // Custom formatter to highlight the matching letters
-    ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
-            var org_sQuery = sQuery;
-            if(this.dataSource.mentionQuery != null){
-                sQuery = this.dataSource.mentionQuery;
-            }
-
-            var query = sQuery.toLowerCase();
-            var _gravatar = function(res, em, size, group){
-                var elem = '<img alt="gravatar" class="perm-gravatar-ac" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, em);
-                if (!em) {
-                    elem = '<i class="icon-user perm-gravatar-ac" style="font-size: {0}px;"></i>'.format(size);
-                }
-                if (group !== undefined){
-                    elem = '<i class="perm-gravatar-ac icon-users"></i>'
-                }
-                var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'
-                return tmpl.format(elem,res)
-            }
-            if (oResultData.nname != undefined) {
-                var fname = oResultData.fname || "";
-                var lname = oResultData.lname || "";
-                var nname = oResultData.nname;
+    var mentionsAC = autocompleteCreate(inputElement, container, matchUsers);
+    mentionsAC.suppressInputUpdate = true;
+    // Overwrite formatResult to take into account mentionQuery
+    mentionsAC.formatResult = function (oResultData, sQuery, sResultMatch) {
+        var org_sQuery = sQuery;
+        if (this.dataSource.mentionQuery != null) {
+            sQuery = this.dataSource.mentionQuery;
+        }
+        return autocompleteFormatter(oResultData, sQuery, sResultMatch);
+    }
 
-                // Guard against null value
-                var fnameMatchIndex = fname.toLowerCase().indexOf(query),
-                    lnameMatchIndex = lname.toLowerCase().indexOf(query),
-                    nnameMatchIndex = nname.toLowerCase().indexOf(query),
-                    displayfname, displaylname, displaynname;
-
-                if (fnameMatchIndex > -1) {
-                    displayfname = highlightMatch(fname, query, fnameMatchIndex);
-                } else {
-                    displayfname = fname;
-                }
-
-                if (lnameMatchIndex > -1) {
-                    displaylname = highlightMatch(lname, query, lnameMatchIndex);
-                } else {
-                    displaylname = lname;
-                }
-
-                if (nnameMatchIndex > -1) {
-                    displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
-                } else {
-                    displaynname = nname ? "(" + nname + ")" : "";
-                }
-
-                return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk, oResultData.gravatar_size);
-            } else {
-                return '';
-            }
-        };
-
-    if(ownerAC.itemSelectEvent){
-        ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
+    // Handler for selection of an entry
+    if(mentionsAC.itemSelectEvent){
+        mentionsAC.itemSelectEvent.subscribe(function (sType, aArgs) {
             var myAC = aArgs[0]; // reference back to the AC instance
             var elLI = aArgs[1]; // reference to the selected LI element
             var oData = aArgs[2]; // object literal of selected item's result data
-            //fill the autocomplete with value
-            if (oData.nname != undefined) {
-                //users
-                //Replace the mention name with replaced
-                var re = new RegExp();
-                var org = myAC.getInputEl().value;
-                var chunks = myAC.dataSource.chunks
-                // replace middle chunk(the search term) with actuall  match
-                chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
-                                              '@'+oData.nname+' ');
-                myAC.getInputEl().value = chunks.join('')
-                myAC.getInputEl().focus(); // Y U NO WORK !?
-            } else {
-                //groups
-                myAC.getInputEl().value = oData.grname;
-                $('#perm_new_member_type').val('users_group');
-            }
+            //Replace the mention name with replaced
+            var re = new RegExp();
+            var org = myAC.getInputEl().value;
+            var chunks = myAC.dataSource.chunks
+            // replace middle chunk(the search term) with actuall  match
+            chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
+                                          '@'+oData.nname+' ');
+            myAC.getInputEl().value = chunks.join('')
+            myAC.getInputEl().focus(); // Y U NO WORK !?
         });
     }
 
     // in this keybuffer we will gather current value of search !
     // since we need to get this just when someone does `@` then we do the
     // search
-    ownerAC.dataSource.chunks = [];
-    ownerAC.dataSource.mentionQuery = null;
+    mentionsAC.dataSource.chunks = [];
+    mentionsAC.dataSource.mentionQuery = null;
 
-    ownerAC.get_mention = function(msg, max_pos) {
+    mentionsAC.get_mention = function(msg, max_pos) {
         var org = msg;
         // Must match utils2.py MENTIONS_REGEX.
         // Only matching on string up to cursor, so it must end with $
@@ -1460,27 +1366,31 @@
         return [null, null];
     };
 
-    var $divid = $('#'+divid);
-    $divid.keyup(function(e){
-            var currentMessage = $divid.val();
-            var currentCaretPosition = $divid[0].selectionStart;
+    var $inputElement = $('#'+inputElement);
+    $inputElement.keyup(function(e){
+            var currentMessage = $inputElement.val();
+            var currentCaretPosition = $inputElement[0].selectionStart;
 
-            var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
+            var unam = mentionsAC.get_mention(currentMessage, currentCaretPosition);
             var curr_search = null;
             if(unam[0]){
                 curr_search = unam[0];
             }
 
-            ownerAC.dataSource.chunks = unam[1];
-            ownerAC.dataSource.mentionQuery = curr_search;
+            mentionsAC.dataSource.chunks = unam[1];
+            mentionsAC.dataSource.mentionQuery = curr_search;
         });
 }
 
 var addReviewMember = function(id,fname,lname,nname,gravatar_link,gravatar_size){
-    var displayname = "{0} {1} ({2})".format(fname, lname, nname);
-    var gravatarelm = '<img alt="gravatar" style="width: {0}px; height: {0}px" src="{1}"/>'.format(gravatar_size, gravatar_link);
-    if (!gravatar_link)
-        gravatarelm = '<i class="icon-user" style="font-size: {0}px;"></i>'.format(gravatar_size);
+    var displayname = nname;
+    if ((fname != "") && (lname != "")) {
+        displayname = "{0} {1} ({2})".format(fname, lname, nname);
+    }
+    var gravatarelm = gravatar(gravatar_link, gravatar_size, "");
+    // WARNING: the HTML below is duplicate with
+    // kallithea/templates/pullrequests/pullrequest_show.html
+    // If you change something here it should be reflected in the template too.
     var element = (
         '     <li id="reviewer_{2}">\n'+
         '       <div class="reviewers_member">\n'+
@@ -1514,149 +1424,26 @@
     $li.find('.reviewer_member_remove').replaceWith('&nbsp;(remove not saved)');
 }
 
-/* activate auto completion of users and groups ... but only used for users as PR reviewers */
-var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) {
-    var myUsers = users_list;
-    var myGroups = groups_list;
-
-    // Define a custom search function for the DataSource of users
-    var matchUsers = function (sQuery) {
-            // Case insensitive matching
-            var query = sQuery.toLowerCase();
-            var i = 0;
-            var l = myUsers.length;
-            var matches = [];
-
-            // Match against each name of each contact
-            for (; i < l; i++) {
-                var contact = myUsers[i];
-                if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
-                     ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
-                     ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
-                    matches[matches.length] = contact;
-                }
-            }
-            return matches;
-        };
+/* activate auto completion of users as PR reviewers */
+var PullRequestAutoComplete = function (inputElement, container, users_list) {
 
-    // Define a custom search function for the DataSource of userGroups
-    var matchGroups = function (sQuery) {
-            // Case insensitive matching
-            var query = sQuery.toLowerCase();
-            var i = 0;
-            var l = myGroups.length;
-            var matches = [];
-
-            // Match against each name of each contact
-            for (; i < l; i++) {
-                matched_group = myGroups[i];
-                if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
-                    matches[matches.length] = matched_group;
-                }
-            }
-            return matches;
-        };
-
-    //match all
-    var matchAll = function (sQuery) {
-            return matchUsers(sQuery);
-        };
-
-    // DataScheme for owner
-    var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
-
-    ownerDS.responseSchema = {
-        fields: ["id", "fname", "lname", "nname", "gravatar_lnk", "gravatar_size"]
+    var matchUsers = function (sQuery) {
+        return autocompleteMatchUsers(sQuery, users_list);
     };
 
-    // Instantiate AutoComplete for mentions
-    var reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
-    reviewerAC.useShadow = false;
-    reviewerAC.resultTypeList = false;
+    var reviewerAC = autocompleteCreate(inputElement, container, matchUsers);
     reviewerAC.suppressInputUpdate = true;
-    reviewerAC.animVert = false;
-    reviewerAC.animHoriz = false;
-    reviewerAC.animSpeed = 0.1;
-
-    // Helper highlight function for the formatter
-    var highlightMatch = function (full, snippet, matchindex) {
-            return full.substring(0, matchindex)
-                + "<span class='match'>"
-                + full.substr(matchindex, snippet.length)
-                + "</span>" + full.substring(matchindex + snippet.length);
-        };
-
-    // Custom formatter to highlight the matching letters
-    reviewerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
-            var org_sQuery = sQuery;
-            if(this.dataSource.mentionQuery != null){
-                sQuery = this.dataSource.mentionQuery;
-            }
 
-            var query = sQuery.toLowerCase();
-            var _gravatar = function(res, em, size, group){
-                var elem = '<img alt="gravatar" class="perm-gravatar-ac" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, em);
-                if (!em) {
-                    elem = '<i class="icon-user perm-gravatar-ac" style="font-size: {0}px;"></i>'.format(size);
-                }
-                if (group !== undefined){
-                    elem = '<i class="perm-gravatar-ac icon-users"></i>'
-                }
-                var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'
-                return tmpl.format(elem,res)
-            }
-            if (oResultData.nname != undefined) {
-                var fname = oResultData.fname || "";
-                var lname = oResultData.lname || "";
-                var nname = oResultData.nname;
-
-                // Guard against null value
-                var fnameMatchIndex = fname.toLowerCase().indexOf(query),
-                    lnameMatchIndex = lname.toLowerCase().indexOf(query),
-                    nnameMatchIndex = nname.toLowerCase().indexOf(query),
-                    displayfname, displaylname, displaynname;
-
-                if (fnameMatchIndex > -1) {
-                    displayfname = highlightMatch(fname, query, fnameMatchIndex);
-                } else {
-                    displayfname = fname;
-                }
-
-                if (lnameMatchIndex > -1) {
-                    displaylname = highlightMatch(lname, query, lnameMatchIndex);
-                } else {
-                    displaylname = lname;
-                }
-
-                if (nnameMatchIndex > -1) {
-                    displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
-                } else {
-                    displaynname = nname ? "(" + nname + ")" : "";
-                }
-
-                return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk, oResultData.gravatar_size);
-            } else {
-                return '';
-            }
-        };
-
-    //members cache to catch duplicates
-    reviewerAC.dataSource.cache = [];
-    // hack into select event
+    // Handler for selection of an entry
     if(reviewerAC.itemSelectEvent){
         reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
-
             var myAC = aArgs[0]; // reference back to the AC instance
             var elLI = aArgs[1]; // reference to the selected LI element
             var oData = aArgs[2]; // object literal of selected item's result data
-
-            //fill the autocomplete with value
-            if (oData.nname != undefined) {
-                addReviewMember(oData.id, oData.fname, oData.lname, oData.nname,
-                                oData.gravatar_lnk, oData.gravatar_size);
-                myAC.dataSource.cache.push(oData.id);
-                $('#user').val('');
-            }
+    
+            addReviewMember(oData.id, oData.fname, oData.lname, oData.nname,
+                            oData.gravatar_lnk, oData.gravatar_size);
+            myAC.getInputEl().value = '';
         });
     }
 }
@@ -1726,7 +1513,7 @@
     var $last_node = $('.last_new_member').last(); // empty tr between last and add
     var next_id = $('.new_members').length;
     $last_node.before($('<tr class="new_members">').append(_html.format(next_id)));
-    _MembersAutoComplete("perm_new_member_name_"+next_id,
+    MembersAutoComplete("perm_new_member_name_"+next_id,
             "perm_container_"+next_id, users_list, groups_list);
 }
 
@@ -2069,7 +1856,7 @@
         nextPageLinkLabel: '&gt;',
         previousPageLinkLabel: '&lt;',
         containers:containers
-    })
+    });
 
     return pagi
 }
@@ -2147,6 +1934,46 @@
         });
 }
 
+/**
+ Branch Sorting callback for select2, modifying the filtered result so prefix
+ matches come before matches in the line.
+ **/
+var branchSort = function(results, container, query) {
+    if (query.term) {
+        return results.sort(function (a, b) {
+            // Put closed branches after open ones (a bit of a hack ...)
+            var aClosed = a.text.indexOf("(closed)") > -1,
+                bClosed = b.text.indexOf("(closed)") > -1;
+            if (aClosed && !bClosed) {
+                return 1;
+            }
+            if (bClosed && !aClosed) {
+                return -1;
+            }
+
+            // Put prefix matches before matches in the line
+            var aPos = a.text.indexOf(query.term),
+                bPos = b.text.indexOf(query.term);
+            if (aPos === 0 && bPos !== 0) {
+                return -1;
+            }
+            if (bPos === 0 && aPos !== 0) {
+                return 1;
+            }
+
+            // Default sorting
+            if (a.text > b.text) {
+                return 1;
+            }
+            if (a.text < b.text) {
+                return -1;
+            }
+            return 0;
+        });
+    }
+    return results;
+};
+
 // global hooks after DOM is loaded
 
 $(document).ready(function(){
--- a/kallithea/templates/admin/auth/auth_settings.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/auth/auth_settings.html	Mon Jul 20 15:07:54 2015 +0200
@@ -37,7 +37,7 @@
                     <li>
                       <div style="padding:3px 0px 3px 0px">
                           <span style="margin: 0px 10px 0px 0px" plugin_id="${plugin_path}" class="toggle-plugin btn btn-mini ${'btn-success' if plugin_path in c.enabled_plugins else ''}">
-                          ${_('enabled') if plugin_path in c.enabled_plugins else _('disabled')}</span>${plugin_path}
+                          ${_('Enabled') if plugin_path in c.enabled_plugins else _('Disabled')}</span>${plugin_path}
                       </div>
                     </li>
                %endfor
@@ -46,8 +46,8 @@
        </div>
     </div>
 
-    %for cnt, module in enumerate(c.auth_plugins):
-        <% pluginName = c.auth_plugins_shortnames[module] %>
+    %for cnt, module in enumerate(c.enabled_plugins):
+        <% pluginName = c.plugin_shortnames[module] %>
         <h1>${_('Plugin')}: ${pluginName}</h1>
         <div class="fields">
         ## autoform generation, based on plugin definition from it's settings
@@ -58,7 +58,7 @@
             <div class="field">
                 <div class="label"><label for="${fullsetting}">${_(displayname)}</label></div>
                 <div class="input">
-                    ${h.password(fullsetting,class_='small',autocomplete="off")}
+                    ${h.password(fullsetting,class_='small')}
                     <span class="help-block">${setting["description"]}</span>
                 </div>
             </div>
@@ -122,7 +122,6 @@
             $cur_button.html(_TM['disabled']);
         }
         else{
-            console.log(elems);
             if(elems.indexOf(plugin_id) == -1){
                 elems.push(plugin_id);
             }
--- a/kallithea/templates/admin/defaults/defaults.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/defaults/defaults.html	Mon Jul 20 15:07:54 2015 +0200
@@ -8,7 +8,7 @@
 <%def name="breadcrumbs_links()">
     ${h.link_to(_('Admin'),h.url('admin_home'))}
     &raquo;
-    ${_('Defaults')}
+    ${_('Repository Defaults')}
 </%def>
 
 <%block name="header_menu">
--- a/kallithea/templates/admin/gists/edit.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/gists/edit.html	Mon Jul 20 15:07:54 2015 +0200
@@ -58,7 +58,7 @@
                     ${h.select('lifetime', '0', c.lifetime_options)}
                     <span class="" style="color: #AAA">
                      %if c.gist.gist_expires == -1:
-                      ${_('Expires')}: ${_('never')}
+                      ${_('Expires')}: ${_('Never')}
                      %else:
                       ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))}
                      %endif
@@ -164,7 +164,7 @@
                         $('#eform').submit();
                       }
                     }
-                  })
+                  });
               });
           </script>
         </div>
--- a/kallithea/templates/admin/gists/index.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/gists/index.html	Mon Jul 20 15:07:54 2015 +0200
@@ -54,7 +54,7 @@
                 ${_('Created')} ${h.age(gist.created_on)} /
                 <span style="color: #AAA">
                   %if gist.gist_expires == -1:
-                   ${_('Expires')}: ${_('never')}
+                   ${_('Expires')}: ${_('Never')}
                   %else:
                    ${_('Expires')}: ${h.age(h.time_to_datetime(gist.gist_expires))}
                   %endif
--- a/kallithea/templates/admin/gists/show.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/gists/show.html	Mon Jul 20 15:07:54 2015 +0200
@@ -44,7 +44,7 @@
                         </div>
                         <div class="left item last" style="color: #AAA">
                          %if c.gist.gist_expires == -1:
-                          ${_('Expires')}: ${_('never')}
+                          ${_('Expires')}: ${_('Never')}
                          %else:
                           ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))}
                          %endif
--- a/kallithea/templates/admin/my_account/my_account.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/my_account/my_account.html	Mon Jul 20 15:07:54 2015 +0200
@@ -33,12 +33,12 @@
            </div>
           </li>
           <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
+          <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('my_account_emails')}">${_('Email Addresses')}</a></li>
           <li class="${'active' if c.active=='password' else ''}"><a href="${h.url('my_account_password')}">${_('Password')}</a></li>
           <li class="${'active' if c.active=='api_keys' else ''}"><a href="${h.url('my_account_api_keys')}">${_('API Keys')}</a></li>
-          <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('my_account_emails')}">${_('My Emails')}</a></li>
-          <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('My Repositories')}</a></li>
-          <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched')}</a></li>
-          <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('My Permissions')}</a></li>
+          <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('Owned Repositories')}</a></li>
+          <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched Repositories')}</a></li>
+          <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('Show Permissions')}</a></li>
         </ul>
     </div>
 
--- a/kallithea/templates/admin/my_account/my_account_api_keys.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/my_account/my_account_api_keys.html	Mon Jul 20 15:07:54 2015 +0200
@@ -5,14 +5,14 @@
         <td>
             <span class="btn btn-mini btn-success disabled">${_('Built-in')}</span>
         </td>
-        <td>${_('expires')}: ${_('never')}</td>
+        <td>${_('Expires')}: ${_('Never')}</td>
         <td>
             ${h.form(url('my_account_api_keys'),method='delete')}
                 ${h.hidden('del_api_key',c.user.api_key)}
                 ${h.hidden('del_api_key_builtin',1)}
                 <button class="btn btn-mini btn-danger" type="submit"
-                        onclick="return confirm('${_('Confirm to reset this api key: %s') % c.user.api_key}');">
-                    ${_('reset')}
+                        onclick="return confirm('${_('Confirm to reset this API key: %s') % c.user.api_key}');">
+                    ${_('Reset')}
                 </button>
             ${h.end_form()}
         </td>
@@ -24,12 +24,12 @@
             <td>${api_key.description}</td>
             <td style="min-width: 80px">
                  %if api_key.expires == -1:
-                  ${_('expires')}: ${_('never')}
+                  ${_('Expires')}: ${_('Never')}
                  %else:
                     %if api_key.expired:
-                        ${_('expired')}: ${h.age(h.time_to_datetime(api_key.expires))}
+                        ${_('Expired')}: ${h.age(h.time_to_datetime(api_key.expires))}
                     %else:
-                        ${_('expires')}: ${h.age(h.time_to_datetime(api_key.expires))}
+                        ${_('Expires')}: ${h.age(h.time_to_datetime(api_key.expires))}
                     %endif
                  %endif
             </td>
@@ -37,16 +37,16 @@
                 ${h.form(url('my_account_api_keys'),method='delete')}
                     ${h.hidden('del_api_key',api_key.api_key)}
                     <button class="btn btn-mini btn-danger" type="submit"
-                            onclick="return confirm('${_('Confirm to remove this api key: %s') % api_key.api_key}');">
+                            onclick="return confirm('${_('Confirm to remove this API key: %s') % api_key.api_key}');">
                         <i class="icon-minus-circled"></i>
-                        ${_('remove')}
+                        ${_('Remove')}
                     </button>
                 ${h.end_form()}
             </td>
           </tr>
         %endfor
     %else:
-    <tr><td><div class="ip">${_('No additional api keys specified')}</div></td></tr>
+    <tr><td><div class="ip">${_('No additional API keys specified')}</div></td></tr>
     %endif
   </table>
 </div>
@@ -58,7 +58,7 @@
         <div class="fields">
              <div class="field">
                 <div class="label">
-                    <label for="description">${_('New api key')}:</label>
+                    <label for="description">${_('New API key')}:</label>
                 </div>
                 <div class="input">
                     ${h.text('description', class_='medium', placeholder=_('Description'))}
@@ -79,5 +79,5 @@
         $("#lifetime").select2({
             'dropdownAutoWidth': true
         });
-    })
+    });
 </script>
--- a/kallithea/templates/admin/my_account/my_account_emails.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/my_account/my_account_emails.html	Mon Jul 20 15:07:54 2015 +0200
@@ -16,7 +16,7 @@
                 ${h.form(url('my_account_emails'),method='delete')}
                     ${h.hidden('del_email_id',em.email_id)}
                     <i class="icon-minus-circled" style="color:#FF4444"></i>
-                    ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
+                    ${h.submit('remove_',_('Delete'),id="remove_email_%s" % em.email_id,
                     class_="action_button", onclick="return  confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
                 ${h.end_form()}
             </td>
--- a/kallithea/templates/admin/my_account/my_account_password.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/my_account/my_account_password.html	Mon Jul 20 15:07:54 2015 +0200
@@ -7,7 +7,7 @@
             <label for="current_password">${_('Current password')}:</label>
         </div>
         <div class="input">
-            ${h.password('current_password',class_='medium',autocomplete="off")}
+            ${h.password('current_password',class_='medium')}
         </div>
      </div>
 
@@ -16,7 +16,7 @@
             <label for="new_password">${_('New password')}:</label>
         </div>
         <div class="input">
-            ${h.password('new_password',class_='medium', autocomplete="off")}
+            ${h.password('new_password',class_='medium')}
         </div>
      </div>
 
@@ -25,7 +25,7 @@
             <label for="password_confirmation">${_('Confirm new password')}:</label>
         </div>
         <div class="input">
-            ${h.password('new_password_confirmation',class_='medium', autocomplete="off")}
+            ${h.password('new_password_confirmation',class_='medium')}
         </div>
      </div>
 
--- a/kallithea/templates/admin/my_account/my_account_profile.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/my_account/my_account_profile.html	Mon Jul 20 15:07:54 2015 +0200
@@ -13,7 +13,7 @@
                 %else:
                 <strong>${_('Avatars are disabled')}</strong>
                 <br/>${c.user.email or _('Missing email, please update your user email address.')}
-                    [${_('current IP')}: ${c.perm_user.ip_addr or "?"}]
+                    [${_('Current IP')}: ${c.ip_addr}]
                 %endif
                </p>
            </div>
--- a/kallithea/templates/admin/my_account/my_account_repos.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/my_account/my_account_repos.html	Mon Jul 20 15:07:54 2015 +0200
@@ -30,7 +30,7 @@
         if (req) {
             req = req.toLowerCase();
             for (i = 0; i<data.length; i++) {
-                var pos = data[i].raw_name.toLowerCase().indexOf(req)
+                var pos = data[i].raw_name.toLowerCase().indexOf(req);
                 if (pos != -1) {
                     filtered.push(data[i]);
                 }
--- a/kallithea/templates/admin/my_account/my_account_watched.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/my_account/my_account_watched.html	Mon Jul 20 15:07:54 2015 +0200
@@ -30,7 +30,7 @@
         if (req) {
             req = req.toLowerCase();
             for (i = 0; i<data.length; i++) {
-                var pos = data[i].raw_name.toLowerCase().indexOf(req)
+                var pos = data[i].raw_name.toLowerCase().indexOf(req);
                 if (pos != -1) {
                     filtered.push(data[i]);
                 }
--- a/kallithea/templates/admin/notifications/show_notification.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/notifications/show_notification.html	Mon Jul 20 15:07:54 2015 +0200
@@ -48,7 +48,7 @@
 var main = "${url('notifications')}";
    $('.delete-notification').click(function(e){
        var notification_id = e.currentTarget.id;
-       deleteNotification(url,notification_id,[function(){window.location=main}])
+       deleteNotification(url,notification_id,[function(){window.location=main}]);
    });
 </script>
 </%def>
--- a/kallithea/templates/admin/permissions/permissions.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/permissions/permissions.html	Mon Jul 20 15:07:54 2015 +0200
@@ -2,13 +2,13 @@
 <%inherit file="/base/base.html"/>
 
 <%block name="title">
-    ${_('Permissions Administration')}
+    ${_('Default Permissions')}
 </%block>
 
 <%def name="breadcrumbs_links()">
     ${h.link_to(_('Admin'),h.url('admin_home'))}
     &raquo;
-    ${_('Permissions')}
+    ${_('Default Permissions')}
 </%def>
 
 <%block name="header_menu">
@@ -27,7 +27,7 @@
       <ul class="nav nav-pills nav-stacked">
         <li class="${'active' if c.active=='globals' else ''}"><a href="${h.url('admin_permissions')}">${_('Global')}</a></li>
         <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('admin_permissions_ips')}">${_('IP Whitelist')}</a></li>
-        <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('admin_permissions_perms')}">${_('Overview')}</a></li>
+        <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('admin_permissions_perms')}">${_('Show Permissions')}</a></li>
       </ul>
     </div>
 
--- a/kallithea/templates/admin/permissions/permissions_globals.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/permissions/permissions_globals.html	Mon Jul 20 15:07:54 2015 +0200
@@ -50,7 +50,7 @@
                     ${h.checkbox('overwrite_default_user_group','true')}
                     <label for="overwrite_default_user_group">
                     <span class="tooltip"
-                    title="${h.tooltip(_('All default permissions on each user group will be reset to chosen permission, note that all custom default permission on repository groups will be lost'))}">
+                    title="${h.tooltip(_('All default permissions on each user group will be reset to chosen permission, note that all custom default permission on user groups will be lost'))}">
                     ${_('Apply to all existing user groups')}</span></label>
                     <span class="help-block">${_('Permissions for the Default user on new user groups.')}</span>
                 </div>
--- a/kallithea/templates/admin/permissions/permissions_ips.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/permissions/permissions_ips.html	Mon Jul 20 15:07:54 2015 +0200
@@ -9,8 +9,8 @@
                 ${h.form(url('edit_user_ips', id=c.user.user_id),method='delete')}
                     ${h.hidden('del_ip_id',ip.ip_id)}
                     ${h.hidden('default_user', 'True')}
-                    <i class="icon-minus-circled" style="color:#FF4444"></i> ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
-                    class_="action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
+                    <i class="icon-minus-circled" style="color:#FF4444"></i> ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
+                    class_="action_button", onclick="return confirm('"+_('Confirm to delete this IP address: %s') % ip.ip_addr+"');")}
                 ${h.end_form()}
               </td>
           </tr>
@@ -27,7 +27,7 @@
         <div class="fields">
              <div class="field">
                 <div class="label">
-                    <label for="new_ip">${_('New ip address')}:</label>
+                    <label for="new_ip">${_('New IP address')}:</label>
                 </div>
                 <div class="input">
                     ${h.hidden('default_user', 'True')}
--- a/kallithea/templates/admin/repo_groups/repo_group_add.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repo_groups/repo_group_add.html	Mon Jul 20 15:07:54 2015 +0200
@@ -76,7 +76,7 @@
     $(document).ready(function(){
         var setCopyPermsOption = function(group_val){
             if(group_val != "-1"){
-                $('#copy_perms').show()
+                $('#copy_perms').show();
             }
             else{
                 $('#copy_perms').hide();
@@ -85,11 +85,11 @@
         $("#group_parent_id").select2({
             'dropdownAutoWidth': true
         });
-        setCopyPermsOption($('#group_parent_id').val())
+        setCopyPermsOption($('#group_parent_id').val());
         $("#group_parent_id").on("change", function(e) {
-            setCopyPermsOption(e.val)
-        })
+            setCopyPermsOption(e.val);
+        });
         $('#group_name').focus();
-    })
+    });
 </script>
 </%def>
--- a/kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html	Mon Jul 20 15:07:54 2015 +0200
@@ -7,7 +7,7 @@
     (_('Total repositories'), c.repo_group.repositories_recursive_count, ''),
     (_('Children groups'), c.repo_group.children.count(), ''),
     (_('Created on'), h.fmt_date(c.repo_group.created_on), ''),
-    (_('Owner'), h.person(c.repo_group.user), '')
+    (_('Owner'), h.person(c.repo_group.user), ''),
  ]
 %>
 %for dt, dd, tt in elems:
--- a/kallithea/templates/admin/repo_groups/repo_group_edit_perms.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repo_groups/repo_group_edit_perms.html	Mon Jul 20 15:07:54 2015 +0200
@@ -4,11 +4,11 @@
         <div class="field">
             <table id="permissions_manage" class="noborder">
                 <tr>
-                    <td>${_('none')}</td>
-                    <td>${_('read')}</td>
-                    <td>${_('write')}</td>
-                    <td>${_('admin')}</td>
-                    <td>${_('user/user group')}</td>
+                    <td>${_('None')}</td>
+                    <td>${_('Read')}</td>
+                    <td>${_('Write')}</td>
+                    <td>${_('Admin')}</td>
+                    <td>${_('User/User Group')}</td>
                     <td></td>
                 </tr>
                 ## USERS
@@ -25,13 +25,13 @@
                             %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default':
                              <a href="${h.url('edit_user',id=r2p.user.user_id)}">${r2p.user.username}</a>
                             %else:
-                             ${r2p.user.username if r2p.user.username != 'default' else _('default')}
+                             ${r2p.user.username if r2p.user.username != 'default' else _('Default')}
                             %endif
                         </td>
                         <td>
                           %if r2p.user.username !='default':
                             <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
-                             <i class="icon-minus-circled"></i> ${_('revoke')}
+                             <i class="icon-minus-circled"></i> ${_('Revoke')}
                             </span>
                           %endif
                         </td>
@@ -42,9 +42,9 @@
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")}</td>
                         <td style="white-space: nowrap;">
                             ${h.gravatar(r2p.user.email, cls="perm-gravatar", size=14)}
-                            ${r2p.user.username if r2p.user.username != 'default' else _('default')}
+                            ${r2p.user.username if r2p.user.username != 'default' else _('Default')}
                         </td>
-                        <td><i class="icon-user"></i> ${_('delegated admin')}</td>
+                        <td><i class="icon-user"></i> ${_('Delegated Admin')}</td>
                         %endif
                     </tr>
                 %endfor
@@ -68,7 +68,7 @@
                         </td>
                         <td>
                             <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}', '${g2p.users_group.users_group_name}')">
-                            <i class="icon-minus-circled"></i> ${_('revoke')}
+                            <i class="icon-minus-circled"></i> ${_('Revoke')}
                             </span>
                         </td>
                     </tr>
@@ -100,7 +100,7 @@
                 </tr>
                 <tr>
                     <td colspan="6">
-                       ${_('apply to children')}:
+                       ${_('Apply to children')}:
                        ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
                        ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
                        ${h.radio('recursive', 'repos', label=_('Repositories'))}
--- a/kallithea/templates/admin/repo_groups/repo_group_edit_settings.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repo_groups/repo_group_edit_settings.html	Mon Jul 20 15:07:54 2015 +0200
@@ -61,5 +61,5 @@
         $("#group_parent_id").select2({
             'dropdownAutoWidth': true
         });
-    })
+    });
 </script>
--- a/kallithea/templates/admin/repos/repo_add_base.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repos/repo_add_base.html	Mon Jul 20 15:07:54 2015 +0200
@@ -10,21 +10,17 @@
             </div>
             <div class="input">
                 ${h.text('repo_name',class_="small")}
-                <div style="margin: 6px 0px 0px 0px">
-                    <a id="remote_clone_toggle" href="#"><i class="icon-download-cloud"></i> ${_('Import existing repository ?')}</a>
-                </div>
-                %if not c.authuser.is_admin:
-                    ${h.hidden('user_created',True)}
-                %endif
             </div>
          </div>
-        <div id="remote_clone" class="field" style="display: none">
+        <div id="remote_clone" class="field">
             <div class="label">
-                <label for="clone_uri">${_('Clone from')}:</label>
+                <label for="clone_uri">${_('Clone remote repository')}:</label>
             </div>
             <div class="input">
                 ${h.text('clone_uri',class_="small")}
-                <span class="help-block">${_('Optional URL from which repository should be cloned.')}</span>
+                <span class="help-block">
+                    ${_('Optional: URL of a remote repository. If set, the repository will be created as a clone from this URL.')}
+                </span>
             </div>
         </div>
         <div class="field">
@@ -90,31 +86,21 @@
     $(document).ready(function(){
         var setCopyPermsOption = function(group_val){
             if(group_val != "-1"){
-                $('#copy_perms').show()
+                $('#copy_perms').show();
             }
             else{
                 $('#copy_perms').hide();
             }
         }
 
-        $('#remote_clone_toggle').on('click', function(e){
-            $('#remote_clone').show();
-            e.preventDefault();
-        })
-        if($('#remote_clone input').hasClass('error')){
-            $('#remote_clone').show();
-        }
-        if($('#remote_clone input').val()){
-            $('#remote_clone').show();
-        }
         $("#repo_group").select2({
             'dropdownAutoWidth': true
         });
 
-        setCopyPermsOption($('#repo_group').val())
+        setCopyPermsOption($('#repo_group').val());
         $("#repo_group").on("change", function(e) {
-            setCopyPermsOption(e.val)
-        })
+            setCopyPermsOption(e.val);
+        });
 
         $("#repo_type").select2({
             'minimumResultsForSearch': -1
@@ -123,6 +109,6 @@
             'minimumResultsForSearch': -1
         });
         $('#repo_name').focus();
-    })
+    });
 </script>
 ${h.end_form()}
--- a/kallithea/templates/admin/repos/repo_creating.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repos/repo_creating.html	Mon Jul 20 15:07:54 2015 +0200
@@ -57,7 +57,7 @@
           setTimeout(worker, 1000);
       }
       else{
-          $("#progress").html($('#progress_error').html())
+          $("#progress").html($('#progress_error').html());
       }
     }
   });
--- a/kallithea/templates/admin/repos/repo_edit_advanced.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repos/repo_edit_advanced.html	Mon Jul 20 15:07:54 2015 +0200
@@ -16,7 +16,7 @@
         $("#id_fork_of").select2({
             'dropdownAutoWidth': true
         });
-    })
+    });
 </script>
 
 <h3>${_('Public Journal Visibility')}</h3>
--- a/kallithea/templates/admin/repos/repo_edit_fields.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repos/repo_edit_fields.html	Mon Jul 20 15:07:54 2015 +0200
@@ -15,7 +15,7 @@
             <td>
               ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
                   <i class="icon-minus-circled" style="color:#FF4444"></i>
-                  ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id,
+                  ${h.submit('remove_%s' % field.repo_field_id, _('Delete'), id="remove_field_%s" % field.repo_field_id,
                   class_="action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
               ${h.end_form()}
             </td>
--- a/kallithea/templates/admin/repos/repo_edit_fork.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repos/repo_edit_fork.html	Mon Jul 20 15:07:54 2015 +0200
@@ -17,5 +17,5 @@
         $("#id_fork_of").select2({
             'dropdownAutoWidth': true
         });
-    })
+    });
 </script>
--- a/kallithea/templates/admin/repos/repo_edit_permissions.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repos/repo_edit_permissions.html	Mon Jul 20 15:07:54 2015 +0200
@@ -5,11 +5,11 @@
             ${h.hidden('repo_private')}
             <table id="permissions_manage" class="noborder">
                 <tr>
-                    <td>${_('none')}</td>
-                    <td>${_('read')}</td>
-                    <td>${_('write')}</td>
-                    <td>${_('admin')}</td>
-                    <td>${_('user/user group')}</td>
+                    <td>${_('None')}</td>
+                    <td>${_('Read')}</td>
+                    <td>${_('Write')}</td>
+                    <td>${_('Admin')}</td>
+                    <td>${_('User/User Group')}</td>
                     <td></td>
                 </tr>
                 ## USERS
@@ -18,10 +18,10 @@
                         <tr>
                             <td colspan="4">
                                 <span class="private_repo_msg">
-                                ${_('private repository')}
+                                ${_('Private Repository')}
                                 </span>
                             </td>
-                            <td class="private_repo_msg"><i class="icon-user"></i> ${_('default')}</td>
+                            <td class="private_repo_msg"><i class="icon-user"></i> ${_('Default')}</td>
                         </tr>
                     %else:
                     <tr id="id${id(r2p.user.username)}">
@@ -34,13 +34,13 @@
                             %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default':
                              <a href="${h.url('edit_user',id=r2p.user.user_id)}">${r2p.user.username}</a>
                             %else:
-                             ${r2p.user.username if r2p.user.username != 'default' else _('default')}
+                             ${r2p.user.username if r2p.user.username != 'default' else _('Default')}
                             %endif
                         </td>
                         <td>
                           %if r2p.user.username !='default':
                             <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
-                            <i class="icon-minus-circled"></i> ${_('revoke')}
+                            <i class="icon-minus-circled"></i> ${_('Revoke')}
                             </span>
                           %endif
                         </td>
@@ -65,7 +65,7 @@
                         </td>
                         <td>
                             <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}', '${g2p.users_group.users_group_name}')">
-                            <i class="icon-minus-circled"></i> ${_('revoke')}
+                            <i class="icon-minus-circled"></i> ${_('Revoke')}
                             </span>
                         </td>
                     </tr>
--- a/kallithea/templates/admin/repos/repo_edit_remote.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repos/repo_edit_remote.html	Mon Jul 20 15:07:54 2015 +0200
@@ -1,16 +1,19 @@
 %if c.repo_info.clone_uri:
 <div style="font-size: 20px; padding: 0px 0px 10px 0px">
-   ${_('Remote URL')}: <a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri_hidden}</a></li>
+   ${_('Remote repository URL')}: <a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri_hidden}</a></li>
 </div>
 ${h.form(url('edit_repo_remote', repo_name=c.repo_name), method='put')}
 <div class="form">
-   <div class="fields">
-       ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull Changes from Remote Location'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to pull changes from remote side.')+"');")}
-   </div>
+    <div class="fields">
+        ${h.submit('remote_pull_%s' % c.repo_info.repo_name,
+            _('Pull Changes from Remote Repository'),
+            class_="btn btn-small",
+            onclick="return confirm('"+_('Confirm to pull changes from remote repository.')+"');")}
+    </div>
 </div>
 ${h.end_form()}
 %else:
   <div style="font-size: 20px">
-    ${_('This repository does not have a remote URL set.')}
+    ${_('This repository does not have a remote repository URL.')}
   </div>
 %endif
--- a/kallithea/templates/admin/repos/repo_edit_settings.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/repos/repo_edit_settings.html	Mon Jul 20 15:07:54 2015 +0200
@@ -15,27 +15,29 @@
                                Using the above permanent URL guarantees that this repository always will be accessible on that URL.
                                This is useful for CI systems, or any other cases that you need to hardcode the URL into a 3rd party service.''')}</span>
                 </div>
-           </div>
-           <div class="field">
-               <div class="label">
-                   <label for="clone_uri">${_('Clone URL')}:</label>
-               </div>
-               <div class="input">
-                   %if c.repo_info.clone_uri:
+            </div>
+            <div class="field">
+                <div class="label">
+                    <label for="clone_uri">${_('Remote repository')}:</label>
+                </div>
+                <div class="input">
+                  %if c.repo_info.clone_uri:
                     <div id="clone_uri_hidden" style="font-size: 14px">
                         <span id="clone_uri_hidden_value">${c.repo_info.clone_uri_hidden}</span>
-                        <span style="cursor: pointer; padding: 0px 0px 5px 0px" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
+                        <span style="cursor: pointer; padding: 0px 0px 5px 0px" id="edit_clone_uri"><i class="icon-edit"></i>${_('Edit')}</span>
                     </div>
                     <div id="alter_clone_uri" style="display: none">
-                        ${h.text('clone_uri',class_="medium",  placeholder=_('new value'))}
+                        ${h.text('clone_uri',class_="medium", placeholder=_('New URL'))}
                     </div>
-                   %else:
+                  %else:
                     ## not set yet, display form to set it
                     ${h.text('clone_uri',class_="medium")}
                     ${h.hidden('clone_uri_change', 'NEW')}
-                   %endif
-                 <span id="alter_clone_uri_help_block" class="help-block">${_('URL used for doing remote pulls.')}</span>
-               </div>
+                  %endif
+                  <span id="alter_clone_uri_help_block" class="help-block">
+                    ${_('Optional: URL of a remote repository. If set, the repository can be pulled from this URL.')}
+                  </span>
+                </div>
             </div>
             <div class="field">
                 <div class="label">
@@ -143,15 +145,15 @@
         $('#show_more_clone_id').on('click', function(e){
             $('#clone_id').show();
             e.preventDefault();
-        })
+        });
         $('#edit_clone_uri').on('click', function(e){
           $('#alter_clone_uri').show();
           $('#edit_clone_uri').hide();
           $('#clone_uri_hidden').hide();
           ## store hash of old value for change detection
-          var uri_change =  '<input id="clone_uri_change" name="clone_uri_change" type="hidden" value="${h.md5(c.repo_info.clone_uri or "").hexdigest()}" />';
-          $('#alter_clone_uri_help_block').html($('#alter_clone_uri_help_block').html()+" ("+$('#clone_uri_hidden_value').html()+")")
-        })
+          var uri_change = '<input id="clone_uri_change" name="clone_uri_change" type="hidden" value="${h.md5(c.repo_info.clone_uri or "").hexdigest()}" />';
+          $('#alter_clone_uri_help_block').html($('#alter_clone_uri_help_block').html()+" ("+$('#clone_uri_hidden_value').html()+")");
+        });
 
         $('#repo_landing_rev').select2({
             'dropdownAutoWidth': true
@@ -160,5 +162,8 @@
             'dropdownAutoWidth': true
         });
 
-    })
+        // autocomplete
+        var _USERS_AC_DATA = ${c.users_array|n};
+        SimpleUserAutoComplete('user', 'owner_container', _USERS_AC_DATA);
+    });
 </script>
--- a/kallithea/templates/admin/settings/settings_email.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/settings/settings_email.html	Mon Jul 20 15:07:54 2015 +0200
@@ -13,7 +13,7 @@
 
     (_('SMTP use TLS'), c.ini.get('smtp_use_tls'), ''),
     (_('SMTP use SSL'), c.ini.get('smtp_use_ssl'), ''),
-    (_('SMTP auth'), c.ini.get('smtp_auth'), '')
+    (_('SMTP auth'), c.ini.get('smtp_auth'), ''),
  ]
 %>
 %for dt, dd, tt in elems:
--- a/kallithea/templates/admin/settings/settings_hooks.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/settings/settings_hooks.html	Mon Jul 20 15:07:54 2015 +0200
@@ -33,7 +33,7 @@
             <span class="action_button"
             onclick="ajaxActionHook(${hook.ui_id},'${'id%s' % hook.ui_id }')">
             <i class="icon-minus-circled" style="color:#FF4444"></i>
-            ${_('delete')}
+            ${_('Delete')}
             </span>
         </div>
       </div>
--- a/kallithea/templates/admin/settings/settings_system.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/settings/settings_system.html	Mon Jul 20 15:07:54 2015 +0200
@@ -1,12 +1,12 @@
 <dl class="dl-horizontal">
 <%
  elems = [
-    (_('Kallithea version'), h.literal('%s <b><span style="color:#036185; text-decoration: underline;cursor: pointer" id="check_for_update" >%s</span></b>' % (c.kallithea_version, _('check for updates'))), ''),
+    (_('Kallithea version'), h.literal('%s <b><span style="color:#036185; text-decoration: underline;cursor: pointer" id="check_for_update" >%s</span></b>' % (c.kallithea_version, _('Check for updates'))), ''),
     (_('Python version'), c.py_version, ''),
     (_('Platform'), c.platform, ''),
     (_('Git version'), c.git_version, ''),
     (_('Git path'), c.ini.get('git_path'), ''),
-    (_('Upgrade info endpoint'), h.literal('%s <br/><span style="color:#999999">%s.</span>' % (c.update_url, _('Note: please make sure this server can access this URL'))), '')
+    (_('Upgrade info endpoint'), h.literal('%s <br/><span style="color:#999999">%s.</span>' % (c.update_url, _('Note: please make sure this server can access this URL'))), ''),
  ]
 %>
 
--- a/kallithea/templates/admin/settings/settings_vcs.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/settings/settings_vcs.html	Mon Jul 20 15:07:54 2015 +0200
@@ -91,6 +91,6 @@
                 $('#path_unlock_icon').addClass('icon-lock-open-alt');
                 $('#paths_root_path').removeAttr('readonly');
                 $('#paths_root_path').removeClass('disabled');
-            })
-        })
+            });
+        });
     </script>
--- a/kallithea/templates/admin/user_groups/user_group_add.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/user_groups/user_group_add.html	Mon Jul 20 15:07:54 2015 +0200
@@ -64,6 +64,6 @@
 <script>
     $(document).ready(function(){
         $('#users_group_name').focus();
-    })
+    });
 </script>
 </%def>
--- a/kallithea/templates/admin/user_groups/user_group_edit.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/user_groups/user_group_edit.html	Mon Jul 20 15:07:54 2015 +0200
@@ -28,9 +28,9 @@
       <ul class="nav nav-pills nav-stacked">
         <li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_users_group', id=c.user_group.users_group_id)}">${_('Settings')}</a></li>
         <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_group_advanced', id=c.user_group.users_group_id)}">${_('Advanced')}</a></li>
-        <li class="${'active' if c.active=='default_perms' else ''}"><a href="${h.url('edit_user_group_default_perms', id=c.user_group.users_group_id)}">${_('Default permissions')}</a></li>
         <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_group_perms', id=c.user_group.users_group_id)}">${_('Permissions')}</a></li>
-        <li class="${'active' if c.active=='members' else ''}"><a href="${h.url('edit_user_group_members', id=c.user_group.users_group_id)}">${_('Members')}</a></li>
+        <li class="${'active' if c.active=='default_perms' else ''}"><a href="${h.url('edit_user_group_default_perms', id=c.user_group.users_group_id)}">${_('Show Permissions')}</a></li>
+        <li class="${'active' if c.active=='members' else ''}"><a href="${h.url('edit_user_group_members', id=c.user_group.users_group_id)}">${_('Show Members')}</a></li>
       </ul>
     </div>
 
--- a/kallithea/templates/admin/user_groups/user_group_edit_advanced.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/user_groups/user_group_edit_advanced.html	Mon Jul 20 15:07:54 2015 +0200
@@ -5,7 +5,7 @@
  elems = [
     (_('Members'), len(c.group_members_obj), ''),
     (_('Created on'), h.fmt_date(c.user_group.created_on), ''),
-    (_('Owner'), h.person(c.user_group.user), '')
+    (_('Owner'), h.person(c.user_group.user), ''),
     ]
 %>
 %for dt, dd, tt in elems:
--- a/kallithea/templates/admin/user_groups/user_group_edit_perms.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/user_groups/user_group_edit_perms.html	Mon Jul 20 15:07:54 2015 +0200
@@ -4,11 +4,11 @@
         <div class="field">
             <table id="permissions_manage" class="noborder">
                 <tr>
-                    <td>${_('none')}</td>
-                    <td>${_('read')}</td>
-                    <td>${_('write')}</td>
-                    <td>${_('admin')}</td>
-                    <td>${_('user/user group')}</td>
+                    <td>${_('None')}</td>
+                    <td>${_('Read')}</td>
+                    <td>${_('Write')}</td>
+                    <td>${_('Admin')}</td>
+                    <td>${_('User/User Group')}</td>
                     <td></td>
                 </tr>
                 ## USERS
@@ -25,13 +25,13 @@
                             %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default':
                              <a href="${h.url('edit_user',id=r2p.user.user_id)}">${r2p.user.username}</a>
                             %else:
-                             ${r2p.user.username if r2p.user.username != 'default' else _('default')}
+                             ${r2p.user.username if r2p.user.username != 'default' else _('Default')}
                             %endif
                         </td>
                         <td>
                           %if r2p.user.username !='default':
                             <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
-                             <i class="icon-minus-circled"></i> ${_('revoke')}
+                             <i class="icon-minus-circled"></i> ${_('Revoke')}
                             </span>
                           %endif
                         </td>
@@ -42,9 +42,9 @@
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin', disabled="disabled")}</td>
                         <td style="white-space: nowrap;">
                             ${h.gravatar(r2p.user.email, cls="perm-gravatar", size=14)}
-                            ${r2p.user.username if r2p.user.username != 'default' else _('default')}
+                            ${r2p.user.username if r2p.user.username != 'default' else _('Default')}
                         </td>
-                        <td><i class="icon-user"></i> ${_('delegated admin')}</td>
+                        <td><i class="icon-user"></i> ${_('Delegated Admin')}</td>
                         %endif
                     </tr>
                 %endfor
@@ -68,7 +68,7 @@
                         </td>
                         <td>
                             <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${g2p.user_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.user_group.users_group_name)}', '${g2p.user_group.users_group_name}')">
-                            <i class="icon-minus-circled"></i> ${_('revoke')}
+                            <i class="icon-minus-circled"></i> ${_('Revoke')}
                             </span>
                         </td>
                     </tr>
--- a/kallithea/templates/admin/user_groups/user_groups.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/user_groups/user_groups.html	Mon Jul 20 15:07:54 2015 +0200
@@ -7,7 +7,7 @@
 
 <%def name="breadcrumbs_links()">
     <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
-    ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
+    ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('User Groups')}
 </%def>
 
 <%block name="header_menu">
--- a/kallithea/templates/admin/users/user_add.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/users/user_add.html	Mon Jul 20 15:07:54 2015 +0200
@@ -41,7 +41,7 @@
                     <label for="password">${_('Password')}:</label>
                 </div>
                 <div class="input">
-                    ${h.password('password',class_='small',autocomplete="off")}
+                    ${h.password('password',class_='small')}
                 </div>
              </div>
 
@@ -50,7 +50,7 @@
                     <label for="password_confirmation">${_('Password confirmation')}:</label>
                 </div>
                 <div class="input">
-                    ${h.password('password_confirmation',class_="small",autocomplete="off")}
+                    ${h.password('password_confirmation',class_="small")}
                 </div>
              </div>
 
@@ -103,6 +103,6 @@
 <script>
     $(document).ready(function(){
         $('#username').focus();
-    })
+    });
 </script>
 </%def>
--- a/kallithea/templates/admin/users/user_edit.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/users/user_edit.html	Mon Jul 20 15:07:54 2015 +0200
@@ -27,11 +27,11 @@
     <div style="width: 150px; float:left">
       <ul class="nav nav-pills nav-stacked">
         <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', id=c.user.user_id)}">${_('Profile')}</a></li>
+        <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', id=c.user.user_id)}">${_('Emails')}</a></li>
         <li class="${'active' if c.active=='api_keys' else ''}"><a href="${h.url('edit_user_api_keys', id=c.user.user_id)}">${_('API Keys')}</a></li>
+        <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', id=c.user.user_id)}">${_('IP Whitelist')}</a></li>
         <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', id=c.user.user_id)}">${_('Advanced')}</a></li>
-        <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_perms', id=c.user.user_id)}">${_('Default Permissions')}</a></li>
-        <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', id=c.user.user_id)}">${_('Emails')}</a></li>
-        <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', id=c.user.user_id)}">${_('IP Whitelist')}</a></li>
+        <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_perms', id=c.user.user_id)}">${_('Show Permissions')}</a></li>
       </ul>
     </div>
 
--- a/kallithea/templates/admin/users/user_edit_advanced.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/users/user_edit_advanced.html	Mon Jul 20 15:07:54 2015 +0200
@@ -7,7 +7,7 @@
     (_('Source of Record'), c.user.extern_type, ''),
     (_('Created on'), h.fmt_date(c.user.created_on), ''),
     (_('Last Login'), c.user.last_login or '-', ''),
-    (_('Member of User groups'), len(c.user.group_member), ', '.join((x.users_group.users_group_name for x in c.user.group_member)))
+    (_('Member of User groups'), len(c.user.group_member), ', '.join((x.users_group.users_group_name for x in c.user.group_member))),
  ]
 %>
 %for dt, dd, tt in elems:
--- a/kallithea/templates/admin/users/user_edit_api_keys.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/users/user_edit_api_keys.html	Mon Jul 20 15:07:54 2015 +0200
@@ -5,14 +5,14 @@
         <td>
             <span class="btn btn-mini btn-success disabled">${_('Built-in')}</span>
         </td>
-        <td>${_('expires')}: ${_('never')}</td>
+        <td>${_('Expires')}: ${_('Never')}</td>
         <td>
             ${h.form(url('edit_user_api_keys', id=c.user.user_id),method='delete')}
                 ${h.hidden('del_api_key',c.user.api_key)}
                 ${h.hidden('del_api_key_builtin',1)}
                 <button class="btn btn-mini btn-danger" type="submit"
-                        onclick="return confirm('${_('Confirm to reset this api key: %s') % c.user.api_key}');">
-                    ${_('reset')}
+                        onclick="return confirm('${_('Confirm to reset this API key: %s') % c.user.api_key}');">
+                    ${_('Reset')}
                 </button>
             ${h.end_form()}
         </td>
@@ -24,12 +24,12 @@
             <td>${api_key.description}</td>
             <td style="min-width: 80px">
                  %if api_key.expires == -1:
-                  ${_('expires')}: ${_('never')}
+                  ${_('Expires')}: ${_('Never')}
                  %else:
                     %if api_key.expired:
-                        ${_('expired')}: ${h.age(h.time_to_datetime(api_key.expires))}
+                        ${_('Expired')}: ${h.age(h.time_to_datetime(api_key.expires))}
                     %else:
-                        ${_('expires')}: ${h.age(h.time_to_datetime(api_key.expires))}
+                        ${_('Expires')}: ${h.age(h.time_to_datetime(api_key.expires))}
                     %endif
                  %endif
             </td>
@@ -37,28 +37,28 @@
                 ${h.form(url('edit_user_api_keys', id=c.user.user_id),method='delete')}
                     ${h.hidden('del_api_key',api_key.api_key)}
                     <button class="btn btn-mini btn-danger" type="submit"
-                            onclick="return confirm('${_('Confirm to remove this api key: %s') % api_key.api_key}');">
+                            onclick="return confirm('${_('Confirm to remove this API key: %s') % api_key.api_key}');">
                         <i class="icon-minus-circled"></i>
-                        ${_('remove')}
+                        ${_('Remove')}
                     </button>
                 ${h.end_form()}
             </td>
           </tr>
         %endfor
     %else:
-    <tr><td><div class="ip">${_('No additional api keys specified')}</div></td></tr>
+    <tr><td><div class="ip">${_('No additional API keys specified')}</div></td></tr>
     %endif
   </table>
 </div>
 
 <div>
-    ${h.form(url('edit_user_api_keys', id=c.user.user_id), method='put')}
+    ${h.form(url('edit_user_api_keys', id=c.user.user_id), method='post')}
     <div class="form">
         <!-- fields -->
         <div class="fields">
              <div class="field">
                 <div class="label">
-                    <label for="description">${_('New api key')}:</label>
+                    <label for="description">${_('New API key')}:</label>
                 </div>
                 <div class="input">
                     ${h.text('description', class_='medium', placeholder=_('Description'))}
@@ -79,5 +79,5 @@
         $("#lifetime").select2({
             'dropdownAutoWidth': true
         });
-    })
+    });
 </script>
--- a/kallithea/templates/admin/users/user_edit_emails.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/users/user_edit_emails.html	Mon Jul 20 15:07:54 2015 +0200
@@ -16,7 +16,7 @@
                 ${h.form(url('edit_user_emails', id=c.user.user_id),method='delete')}
                     ${h.hidden('del_email_id',em.email_id)}
                     <i class="icon-minus-circled" style="color:#FF4444"></i>
-                    ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
+                    ${h.submit('remove_',_('Delete'),id="remove_email_%s" % em.email_id,
                     class_="action_button", onclick="return  confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
                 ${h.end_form()}
             </td>
--- a/kallithea/templates/admin/users/user_edit_ips.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/users/user_edit_ips.html	Mon Jul 20 15:07:54 2015 +0200
@@ -19,8 +19,8 @@
                 ${h.form(url('edit_user_ips', id=c.user.user_id),method='delete')}
                     ${h.hidden('del_ip_id',ip.ip_id)}
                     <i class="icon-minus-circled" style="color:#FF4444"></i>
-                    ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
-                    class_="action_button", onclick="return  confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
+                    ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
+                    class_="action_button", onclick="return  confirm('"+_('Confirm to delete this IP address: %s') % ip.ip_addr+"');")}
                 ${h.end_form()}
             </td>
           </tr>
@@ -39,7 +39,7 @@
         <div class="fields">
              <div class="field">
                 <div class="label">
-                    <label for="new_ip">${_('New ip address')}:</label>
+                    <label for="new_ip">${_('New IP address')}:</label>
                 </div>
                 <div class="input">
                     ${h.text('new_ip', class_='medium')}
--- a/kallithea/templates/admin/users/user_edit_profile.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/admin/users/user_edit_profile.html	Mon Jul 20 15:07:54 2015 +0200
@@ -12,7 +12,7 @@
                 <br/>${c.user.email or _('Missing email, please update this user email address.')}
                         ##show current ip just if we show ourself
                         %if c.authuser.username == c.user.username:
-                            [${_('current IP')}: ${c.perm_user.ip_addr or "?"}]
+                            [${_('Current IP')}: ${c.ip_addr}]
                         %endif
                 %endif
            </div>
@@ -69,7 +69,7 @@
                     <label for="new_password">${_('New password')}:</label>
                 </div>
                 <div class="input">
-                    ${h.password('new_password',class_='medium%s' % disabled,autocomplete="off",readonly=readonly)}
+                    ${h.password('new_password',class_='medium%s' % disabled,readonly=readonly)}
                 </div>
              </div>
 
@@ -78,7 +78,7 @@
                     <label for="password_confirmation">${_('New password confirmation')}:</label>
                 </div>
                 <div class="input">
-                    ${h.password('password_confirmation',class_="medium%s" % disabled,autocomplete="off",readonly=readonly)}
+                    ${h.password('password_confirmation',class_="medium%s" % disabled,readonly=readonly)}
                 </div>
              </div>
 
--- a/kallithea/templates/base/base.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/base/base.html	Mon Jul 20 15:07:54 2015 +0200
@@ -61,9 +61,9 @@
       <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i> ${_('Repository Groups')}</a></li>
       <li><a href="${h.url('users')}"><i class="icon-user"></i> ${_('Users')}</a></li>
       <li><a href="${h.url('users_groups')}"><i class="icon-users"></i> ${_('User Groups')}</a></li>
-      <li><a href="${h.url('admin_permissions')}"><i class="icon-block"></i> ${_('Permissions')}</a></li>
+      <li><a href="${h.url('admin_permissions')}"><i class="icon-block"></i> ${_('Default Permissions')}</a></li>
       <li><a href="${h.url('auth_home')}"><i class="icon-key"></i> ${_('Authentication')}</a></li>
-      <li><a href="${h.url('defaults')}"><i class="icon-wrench"></i> ${_('Defaults')}</a></li>
+      <li><a href="${h.url('defaults')}"><i class="icon-wrench"></i> ${_('Repository Defaults')}</a></li>
       <li class="last"><a href="${h.url('admin_settings')}"><i class="icon-gear"></i> ${_('Settings')}</a></li>
   </ul>
 
@@ -348,7 +348,10 @@
             <ol class="links">
               <li><a href="${h.url('notifications')}">${_('Notifications')}: ${c.unread_notifications}</a></li>
               <li>${h.link_to(_(u'My Account'),h.url('my_account'))}</li>
-              <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
+              %if not c.authuser.is_external_auth:
+                ## Cannot log out if using external (container) authentication.
+                <li class="logout">${h.link_to(_(u'Log Out'), h.url('logout_home'))}</li>
+              %endif
             </ol>
             </div>
           %endif
@@ -419,11 +422,11 @@
                     var children = [];
                     $.each(this.children, function(){
                         if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
-                            children.push({'id': this.id, 'text': this.text, 'type': this.type, 'obj': this.obj})
+                            children.push({'id': this.id, 'text': this.text, 'type': this.type, 'obj': this.obj});
                         }
-                    })
+                    });
                     if(children.length !== 0){
-                        data.results.push({'text': section, 'children': children})
+                        data.results.push({'text': section, 'children': children});
                     }
 
                 });
@@ -438,21 +441,21 @@
                       cache[key] = data;
                       query.callback({results: data.results});
                     }
-                  })
+                  });
               }
             }
         });
 
         $("#repo_switcher").on('select2-selecting', function(e){
             e.preventDefault();
-            window.location = pyroutes.url('summary_home', {'repo_name': e.val})
-        })
+            window.location = pyroutes.url('summary_home', {'repo_name': e.val});
+        });
 
         ## Global mouse bindings ##
 
         // general help "?"
         Mousetrap.bind(['?'], function(e) {
-            $('#help_kb').modal({})
+            $('#help_kb').modal({});
         });
 
         // / open the quick filter
@@ -544,7 +547,7 @@
                          ('g g', 'Goto my private gists page'),
                          ('g G', 'Goto my public gists page'),
                          ('n r', 'New repository page'),
-                         ('n g', 'New gist page')
+                         ('n g', 'New gist page'),
                      ]
                   %>
                   %for key, desc in elems:
@@ -572,7 +575,7 @@
                          ('g f', 'Goto files page'),
                          ('g F', 'Goto files page with file search activated'),
                          ('g o', 'Goto repository settings'),
-                         ('g O', 'Goto repository permissions settings')
+                         ('g O', 'Goto repository permissions settings'),
                      ]
                   %>
                   %for key, desc in elems:
--- a/kallithea/templates/base/default_perms_box.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/base/default_perms_box.html	Mon Jul 20 15:07:54 2015 +0200
@@ -11,13 +11,13 @@
         <div class="fields">
              <div class="field">
                 <div class="label label-checkbox">
-                    <label for="inherit_default_permissions">${_('Inherit from defaults')}:</label>
+                    <label for="inherit_default_permissions">${_('Inherit defaults')}:</label>
                 </div>
                 <div class="checkboxes">
                     ${h.checkbox('inherit_default_permissions',value=True)}
                     <span class="help-block">
-                    ${h.literal(_('Select to inherit permissions from %s permissions settings, and default IP address whitelist.')
-                                % h.link_to('default global', url('admin_permissions')))}
+                    ${h.literal(_('Select to inherit global settings, IP whitelist and permissions from the %s.')
+                                % h.link_to('default permissions', url('admin_permissions')))}
                     </span>
                 </div>
              </div>
@@ -83,7 +83,7 @@
     $('#inherit_default_permissions').change(function(){
         show_custom_perms($('#inherit_default_permissions').prop('checked'));
     });
-})
+});
 </script>
 
 </%def>
--- a/kallithea/templates/base/perms_summary.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/base/perms_summary.html	Mon Jul 20 15:07:54 2015 +0200
@@ -10,11 +10,11 @@
             ${section.replace("_"," ").capitalize()}
             %if section != 'global':
                 <div style="float: right">
-                ${_('show')}:
-                ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')}   <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
-                ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')}   <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
-                ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
-                ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
+                ${_('Show')}:
+                ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('None')}</span></label>
+                ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('Read')}</span></label>
+                ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('Write')}</span></label>
+                ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('Admin')}</span></label>
                 </div>
             %endif
         </div>
@@ -40,7 +40,7 @@
                       </td>
                       %if actions:
                       <td>
-                           <a href="${h.url('admin_permissions')}">${_('edit')}</a>
+                           <a href="${h.url('admin_permissions')}">${_('Edit')}</a>
                       </td>
                       %endif
                   </tr>
@@ -76,11 +76,11 @@
                       %if actions:
                       <td>
                           %if section == 'repositories':
-                              <a href="${h.url('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
+                              <a href="${h.url('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('Edit')}</a>
                           %elif section == 'repositories_groups':
-                              <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
+                              <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('Edit')}</a>
                           %elif section == 'user_groups':
-                              ##<a href="${h.url('edit_users_group',id=k)}">${_('edit')}</a>
+                              ##<a href="${h.url('edit_users_group',id=k)}">${_('Edit')}</a>
                           %endif
                       </td>
                       %endif
@@ -99,8 +99,6 @@
     $(document).ready(function(){
         var show_empty = function(section){
             var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
-            console.log(visible)
-            console.log($('.section_{0} tr.perm_row:visible'.format(section)))
             if(visible == 0){
                 $('#empty_{0}'.format(section)).show();
             }
@@ -108,15 +106,12 @@
                 $('#empty_{0}'.format(section)).hide();
             }
         }
-        $('.perm_filter').on('change', function(e){
-            var self = this;
-            var section = $(this).attr('section');
+        var update_show = function($checkbox){
+            var section = $checkbox.attr('section');
 
-            var opts = {}
             var elems = $('.filter_' + section).each(function(el){
-                var perm_type = $(this).attr('perm_type');
-                var checked = this.checked;
-                opts[perm_type] = checked;
+                var perm_type = $checkbox.attr('perm_type');
+                var checked = $checkbox.prop('checked');
                 if(checked){
                     $('.'+section+'_'+perm_type).show();
                 }
@@ -125,8 +120,9 @@
                 }
             });
             show_empty(section);
-        })
-
-    })
+        }
+        $('.perm_filter').on('change', function(){update_show($(this));});
+        $('.perm_filter[value=none]').each(function(){this.checked = false; update_show($(this));});
+    });
 </script>
 </%def>
--- a/kallithea/templates/changelog/changelog.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/changelog/changelog.html	Mon Jul 20 15:07:54 2015 +0200
@@ -89,11 +89,11 @@
                           %if c.statuses.get(cs.raw_id):
                             <div class="changeset-status-ico">
                             %if c.statuses.get(cs.raw_id)[2]:
-                              <a class="tooltip" title="${_('Changeset status: %s\nClick to open associated pull request #%s') % (h.changeset_status_lbl(c.statuses.get(cs.raw_id)[0]), c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
+                              <a class="tooltip" title="${_('Changeset status: %s\nClick to open associated pull request %s') % (c.statuses.get(cs.raw_id)[1], c.statuses.get(cs.raw_id)[4])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
                                 <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
                               </a>
                             %else:
-                              <a class="tooltip" title="${_('Changeset status: %s') % h.changeset_status_lbl(c.statuses.get(cs.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
+                              <a class="tooltip" title="${_('Changeset status: %s') % c.statuses.get(cs.raw_id)[1]}" href="${c.comments[cs.raw_id][0].url()}">
                                   <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
                               </a>
                             %endif
@@ -122,7 +122,7 @@
                                     %if c.comments.get(cs.raw_id):
                                         <div class="comments-container">
                                             <div class="comments-cnt" title="${_('Changeset has comments')}">
-                                                <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
+                                                <a href="${c.comments[cs.raw_id][0].url()}">
                                                     ${len(c.comments[cs.raw_id])}
                                                     <i class="icon-comment-discussion"></i>
                                                 </a>
@@ -210,7 +210,7 @@
                         YUD.get('open_new_pr').href = pyroutes.url('pullrequest_home',
                                                                    {'repo_name': '${c.repo_name}',
                                                                     'rev_start': rev_start,
-                                                                    'rev_end': rev_end})
+                                                                    'rev_end': rev_end});
 
                         YUD.setStyle('compare_fork','display','none');
                     }else{
@@ -266,7 +266,8 @@
                 // change branch filter
                 $("#branch_filter").select2({
                     dropdownAutoWidth: true,
-                    minimumInputLength: 1
+                    minimumInputLength: 1,
+                    sortResults: branchSort
                     });
 
                 $("#branch_filter").change(function(e){
--- a/kallithea/templates/changelog/changelog_summary_data.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/changelog/changelog_summary_data.html	Mon Jul 20 15:07:54 2015 +0200
@@ -15,15 +15,15 @@
         <td class="compact">
             <div class="changeset-status-container">
               %if c.statuses.get(cs.raw_id):
-                <div class="changeset-status-ico shortlog">
+                <span class="changeset-status-ico shortlog">
                 %if c.statuses.get(cs.raw_id)[2]:
-                  <a class="tooltip" title="${_('Changeset status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
+                  <a class="tooltip" title="${_('Changeset status: %s\nClick to open associated pull request %s') % (c.statuses.get(cs.raw_id)[1], c.statuses.get(cs.raw_id)[4])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
                     <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
                   </a>
                 %else:
                   <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
                 %endif
-                </div>
+                </span>
               %endif
             </div>
         </td>
@@ -31,7 +31,7 @@
               %if c.comments.get(cs.raw_id,[]):
                <div class="comments-container">
                    <div title="${('comments')}">
-                       <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
+                       <a href="${c.comments[cs.raw_id][0].url()}">
                           <i class="icon-comment"></i>${len(c.comments[cs.raw_id])}
                        </a>
                    </div>
--- a/kallithea/templates/changeset/changeset.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/changeset/changeset.html	Mon Jul 20 15:07:54 2015 +0200
@@ -33,21 +33,21 @@
         <div class="diffblock">
             <div class="parents">
                 <div id="parent_link" class="changeset_hash">
-                    <i style="color:#036185" class="icon-left-open"></i> <a href="#">${_('parent rev.')}</a>
+                    <i style="color:#036185" class="icon-left-open"></i> <a href="#">${_('Parent rev.')}</a>
                 </div>
             </div>
 
             <div class="children">
                 <div id="child_link" class="changeset_hash">
-                    <a href="#">${_('child rev.')}</a> <i style="color:#036185" class="icon-right-open"></i>
+                    <a href="#">${_('Child rev.')}</a> <i style="color:#036185" class="icon-right-open"></i>
                 </div>
             </div>
 
             <div class="code-header banner">
                 <div class="changeset-status-container">
                     %if c.statuses:
-                        <div class="changeset-status-ico"><i class="icon-circle changeset-status-${c.statuses[0]}"></i></div>
-                        <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
+                        <span class="changeset-status-ico"><i class="icon-circle changeset-status-${c.statuses[0]}"></i></span>
+                        <span title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</span>
                     %endif
                 </div>
                 <div class="diff-actions">
@@ -86,7 +86,7 @@
 
                     <span class="logtags">
                         %if len(c.changeset.parents)>1:
-                        <span class="merge">${_('merge')}</span>
+                        <span class="merge">${_('Merge')}</span>
                         %endif
 
                         %if h.is_hg(c.db_repo_scm_instance):
@@ -114,7 +114,7 @@
                          <div class="gravatar">
                            ${h.gravatar(h.email_or_none(c.changeset.author), size=20)}
                          </div>
-                         <span><b>${h.person(c.changeset.author,'username_and_name')}</b> - ${h.age(c.changeset.date,True)} ${h.fmt_date(c.changeset.date)}</span><br/>
+                         <span><b>${h.person(c.changeset.author,'full_name_and_username')}</b> - ${h.age(c.changeset.date,True)} ${h.fmt_date(c.changeset.date)}</span><br/>
                          <span>${h.email_or_none(c.changeset.author)}</span><br/>
                      </div>
                      <% rev = c.changeset.extra.get('source') %>
@@ -158,13 +158,14 @@
     </div>
 
     ## diff block
+    <div class="commentable-diff">
     <%namespace name="diff_block" file="/changeset/diff_block.html"/>
     ${diff_block.diff_block_js()}
     ${diff_block.diff_block(c.changes[c.changeset.raw_id])}
-
     % if c.limited_diff:
       <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff anyway')}</a></h4>
     % endif
+    </div>
 
     ## template for inline comment form
     ${comment.comment_inline_form()}
@@ -205,7 +206,7 @@
           var file_comments = $('.inline-comment-placeholder').toArray();
           renderInlineComments(file_comments);
 
-          linkInlineComments(document.getElementsByClassName('firstlink'), document.getElementsByClassName("comment"));
+          linkInlineComments($('.firstlink'), $('.comment'));
 
           pyroutes.register('changeset_home',
                             "${h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s')}",
@@ -221,7 +222,7 @@
                     success: function(data) {
                       if(data.results.length === 0){
                           $('#child_link').addClass('disabled');
-                          $('#child_link').html('${_('no revisions')}');
+                          $('#child_link').html('${_('No revisions')}');
                       }
                       if(data.results.length === 1){
                           var commit = data.results[0];
@@ -246,7 +247,7 @@
                   });
               e.preventDefault();
               }
-          })
+          });
 
           //prev links
           $('#parent_link').on('click', function(e){
@@ -258,7 +259,7 @@
                     success: function(data) {
                       if(data.results.length === 0){
                           $('#parent_link').addClass('disabled');
-                          $('#parent_link').html('${_('no revisions')}');
+                          $('#parent_link').html('${_('No revisions')}');
                       }
                       if(data.results.length === 1){
                           var commit = data.results[0];
@@ -289,7 +290,7 @@
           if (window.location.hash != "") {
               window.location.href = window.location.href;
           }
-      })
+      });
 
     </script>
 
--- a/kallithea/templates/changeset/changeset_comment_block.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/changeset/changeset_comment_block.html	Mon Jul 20 15:07:54 2015 +0200
@@ -1,4 +1,4 @@
 ## this is a dummy html file for partial rendering on server and sending
 ## generated output via ajax after comment submit
 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
-${comment.comment_block(c.co)}
+${comment.comment_block(c.comment)}
--- a/kallithea/templates/changeset/changeset_file_comment.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/changeset/changeset_file_comment.html	Mon Jul 20 15:07:54 2015 +0200
@@ -5,53 +5,43 @@
 ##
 <%def name="comment_block(co)">
   <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
+    <div class="comment-prev-next-links"></div>
     <div class="comment-wrapp">
       <div class="meta">
           <div style="float:left">
                ${h.gravatar(co.author.email, size=20)}
           </div>
           <div class="user">
-              ${co.author.username}
-          </div>
-          <div class="date">
-              ${h.age(co.modified_at)}
+              ${co.author.full_name_and_username}
           </div>
 
-       <div style="float:left;padding:4px 0px 0px 5px">
-        <span class="">
-         %if co.pull_request:
-            %if co.status_change:
-              ${_('Status change from pull request')}
-              <a href="${co.pull_request.url()}">"${co.pull_request.title or _("No title")}"</a>:
-            %else:
-              ${_('Comment from pull request')}
-              <a href="${co.pull_request.url()}">"${co.pull_request.title or _("No title")}"</a>
-            %endif
-         %else:
-            %if co.status_change:
-              ${_('Status change on changeset')}:
-            %else:
-              ${_('Comment on changeset')}
-            %endif
-         %endif
-        </span>
-       </div>
+          <span>
+              ${h.age(co.modified_at)}
+              %if co.pull_request:
+                ${_('on pull request')}
+                <a href="${co.pull_request.url()}">"${co.pull_request.title or _("No title")}"</a>
+              %else:
+                ${_('on this changeset')}
+              %endif
+              <a class="permalink" href="${co.url()}">&para;</a>
+          </span>
 
+          %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or co.author.user_id == c.authuser.user_id:
+            <div onClick="confirm('${_("Delete comment?")}') && deleteComment(${co.comment_id})" class="buttons delete-comment btn btn-mini">${_('Delete')}</div>
+          %endif
+      </div>
+      <div class="text">
         %if co.status_change:
-           <div  style="float:left" class="changeset-status-container">
-             <div style="float:left;padding:10px 2px 0px 2px"></div>
-             <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change[0].status_lbl}</div>
-             <div class="changeset-status-ico"><i class="icon-circle changeset-status-${co.status_change[0].status}"></i></div>
+           <div class="rst-block automatic-comment">
+             <p>
+               <span title="${_('Changeset status')}" class="changeset-status-lbl">${_("Status change")}: ${co.status_change[0].status_lbl}</span>
+               <span class="changeset-status-ico"><i class="icon-circle changeset-status-${co.status_change[0].status}"></i></span>
+             </p>
            </div>
         %endif
-
-      <a class="permalink" href="#comment-${co.comment_id}">&para;</a>
-      %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or co.author.user_id == c.authuser.user_id:
-          <div onClick="confirm('${_("Delete comment?")}') && deleteComment(${co.comment_id})" class="buttons delete-comment btn btn-mini">${_('Delete')}</div>
-      %endif
-      </div>
-      <div class="text">
+        %if co.text:
           ${h.rst_w_mentions(co.text)|n}
+        %endif
       </div>
     </div>
   </div>
@@ -118,36 +108,30 @@
     <span class="firstlink"></span>
 </%def>
 
-## generates inlines taken from c.comments var
-<%def name="inlines()">
-    <div class="comments-number">
-        ${comment_count(c.inline_cnt, len(c.comments))}
-    </div>
-    %for path, lines in c.inline_comments:
-        % for line,comments in lines.iteritems():
-            <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
-            %for co in comments:
-                ${comment_block(co)}
-            %endfor
-            </div>
-        %endfor
-    %endfor
-
-</%def>
 
 ## generate inline comments and the main ones
 <%def name="generate_comments()">
 <div class="comments">
-    <div id="inline-comments-container">
-    ## generate inlines for this changeset
-     ${inlines()}
-    </div>
+  <div class="comments-number">
+    ${comment_count(c.inline_cnt, len(c.comments))}
+  </div>
+  <div id="inline-comments-container">
+    %for path, lines in c.inline_comments:
+        %for line, comments in lines.iteritems():
+          <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
+            %for co in comments:
+                ${comment_block(co)}
+            %endfor
+          </div>
+        %endfor
+    %endfor
+  </div>
 
-    %for co in c.comments:
+  %for co in c.comments:
         <div id="comment-tr-${co.comment_id}">
           ${comment_block(co)}
         </div>
-    %endfor
+  %endfor
 </div>
 </%def>
 
--- a/kallithea/templates/changeset/changeset_range.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/changeset/changeset_range.html	Mon Jul 20 15:07:54 2015 +0200
@@ -85,7 +85,7 @@
              <div class="right">
               <span class="logtags">
                 %if len(cs.parents)>1:
-                <span class="merge">${_('merge')}</span>
+                <span class="merge">${_('Merge')}</span>
                 %endif
                 %if h.is_hg(c.db_repo_scm_instance):
                   %for book in cs.bookmarks:
--- a/kallithea/templates/compare/compare_cs.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/compare/compare_cs.html	Mon Jul 20 15:07:54 2015 +0200
@@ -28,7 +28,7 @@
           %if c.cs_comments.get(cs.raw_id):
               <div class="comments-container">
                   <div class="comments-cnt" title="${_('Changeset has comments')}">
-                      <a href="${h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.cs_comments[cs.raw_id][0].comment_id)}">
+                      <a href="${c.cs_comments[cs.raw_id][0].url()}">
                           ${len(c.cs_comments[cs.raw_id])}
                           <i class="icon-comment"></i>
                       </a>
@@ -36,6 +36,21 @@
               </div>
           %endif
         </td>
+        <td class="changeset-logical-index">
+          <%
+              num_cs = len(c.cs_ranges)
+              index = num_cs - cnt
+              if index == 1:
+                  title = _('First (oldest) changeset in this list')
+              elif index == num_cs:
+                  title = _('Last (most recent) changeset in this list')
+              else:
+                  title = _('Position in this list of changesets')
+          %>
+          <span class="tooltip" title="${title}">
+            ${index}
+          </span>
+        </td>
         <td style="width: 140px"><span class="tooltip" title="${h.tooltip(h.age(cs.date))}">${cs.date}</span></td>
         <td><div class="gravatar" commit_id="${cs.raw_id}">${h.gravatar(h.email_or_none(cs.author), size=14)}</div></td>
         <td><div class="author">${h.person(cs.author)}</div></td>
--- a/kallithea/templates/compare/compare_diff.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/compare/compare_diff.html	Mon Jul 20 15:07:54 2015 +0200
@@ -113,14 +113,14 @@
                 var children = [];
                 $.each(this.children, function(){
                     if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
-                        children.push({'id': this.id, 'text': this.text})
+                        children.push({'id': this.id, 'text': this.text});
                     }
-                })
-                data.results.push({'text': section, 'children': children})
+                });
+                data.results.push({'text': section, 'children': children});
             });
             //push the typed in changeset
             data.results.push({'text':_TM['Specify changeset'],
-                               'children': [{'id': query.term, 'text': query.term, 'type': 'rev'}]})
+                               'children': [{'id': query.term, 'text': query.term, 'type': 'rev'}]});
             query.callback(data);
           }else{
               $.ajax({
@@ -132,7 +132,7 @@
                   cache[key] = data;
                   query.callback(data);
                 }
-              })
+              });
           }
         }
     });
@@ -140,7 +140,7 @@
         placeholder: "${'%s@%s' % (c.cs_repo.repo_name, c.cs_ref_name)}",
         dropdownAutoWidth: true,
         formatSelection: function(obj){
-            return '{0}@{1}'.format("${c.cs_repo.repo_name}", obj.text)
+            return '{0}@{1}'.format("${c.cs_repo.repo_name}", obj.text);
         },
         query: function(query){
           var key = 'cache2';
@@ -153,14 +153,14 @@
                 var children = [];
                 $.each(this.children, function(){
                     if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
-                        children.push({'id': this.id, 'text': this.text})
+                        children.push({'id': this.id, 'text': this.text});
                     }
-                })
-                data.results.push({'text': section, 'children': children})
+                });
+                data.results.push({'text': section, 'children': children});
             });
             //push the typed in changeset
             data.results.push({'text':_TM['Specify changeset'],
-                               'children': [{'id': query.term, 'text': query.term, 'type': 'rev'}]})
+                               'children': [{'id': query.term, 'text': query.term, 'type': 'rev'}]});
             query.callback(data);
           }else{
               $.ajax({
@@ -172,7 +172,7 @@
                   cache[key] = data;
                   query.callback({results: data.results});
                 }
-              })
+              });
           }
         }
     });
--- a/kallithea/templates/data_table/_dt_elements.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/data_table/_dt_elements.html	Mon Jul 20 15:07:54 2015 +0200
@@ -119,14 +119,14 @@
 <%def name="repo_actions(repo_name, super_user=True)">
   <div>
     <div style="float:left; margin-right:5px;" class="grid_edit">
-      <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('edit')}">
-        <i class="icon-pencil"></i> ${h.submit('edit_%s' % repo_name,_('edit'),class_="action_button")}
+      <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
+        <i class="icon-pencil"></i> ${h.submit('edit_%s' % repo_name,_('Edit'),class_="action_button")}
       </a>
     </div>
     <div style="float:left" class="grid_delete">
       ${h.form(h.url('repo', repo_name=repo_name),method='delete')}
         <i class="icon-minus-circled" style="color:#FF4444"></i>
-        ${h.submit('remove_%s' % repo_name,_('delete'),class_="action_button",
+        ${h.submit('remove_%s' % repo_name,_('Delete'),class_="action_button",
         onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
       ${h.end_form()}
     </div>
@@ -147,14 +147,14 @@
 
 <%def name="user_actions(user_id, username)">
  <div style="float:left" class="grid_edit">
-   <a href="${h.url('edit_user',id=user_id)}" title="${_('edit')}">
-     <i class="icon-pencil"></i> ${h.submit('edit_%s' % username,_('edit'),class_="action_button")}
+   <a href="${h.url('edit_user',id=user_id)}" title="${_('Edit')}">
+     <i class="icon-pencil"></i> ${h.submit('edit_%s' % username,_('Edit'),class_="action_button")}
    </a>
  </div>
  <div style="float:left" class="grid_delete">
   ${h.form(h.url('delete_user', id=user_id),method='delete')}
     <i class="icon-minus-circled" style="color:#FF4444"></i>
-    ${h.submit('remove_',_('delete'),id="remove_user_%s" % user_id, class_="action_button",
+    ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="action_button",
     onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
   ${h.end_form()}
  </div>
@@ -164,13 +164,13 @@
  <div style="float:left" class="grid_edit">
     <a href="${h.url('edit_users_group', id=user_group_id)}" title="${_('Edit')}">
     <i class="icon-pencil"></i>
-     ${h.submit('edit_%s' % user_group_name,_('edit'),class_="action_button", id_="submit_user_group_edit")}
+     ${h.submit('edit_%s' % user_group_name,_('Edit'),class_="action_button", id_="submit_user_group_edit")}
     </a>
  </div>
  <div style="float:left" class="grid_delete">
     ${h.form(h.url('users_group', id=user_group_id),method='delete')}
       <i class="icon-minus-circled" style="color:#FF4444"></i>
-      ${h.submit('remove_',_('delete'),id="remove_group_%s" % user_group_id, class_="action_button",
+      ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="action_button",
       onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
     ${h.end_form()}
  </div>
@@ -180,13 +180,13 @@
  <div style="float:left" class="grid_edit">
     <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">
     <i class="icon-pencil"></i>
-     ${h.submit('edit_%s' % repo_group_name, _('edit'),class_="action_button")}
+     ${h.submit('edit_%s' % repo_group_name, _('Edit'),class_="action_button")}
     </a>
  </div>
  <div style="float:left" class="grid_delete">
     ${h.form(h.url('repos_group', group_name=repo_group_name),method='delete')}
         <i class="icon-minus-circled" style="color:#FF4444"></i>
-        ${h.submit('remove_%s' % repo_group_name,_('delete'),class_="action_button",
+        ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="action_button",
         onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
     ${h.end_form()}
  </div>
--- a/kallithea/templates/files/diff_2way.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/files/diff_2way.html	Mon Jul 20 15:07:54 2015 +0200
@@ -75,7 +75,7 @@
         cmsettings: {mode: 'text/plain', readOnly: true, lineWrapping: false, lineNumbers: true},
         lhs: function(setValue) {
             if("${c.node1.is_binary}" == "True"){
-                setValue('Binary file')
+                setValue('Binary file');
             }
             else{
                 $.ajax(orig1_url, {dataType: 'text', success: setValue});
@@ -84,7 +84,7 @@
         },
         rhs: function(setValue) {
             if("${c.node2.is_binary}" == "True"){
-                setValue('Binary file')
+                setValue('Binary file');
             }
             else{
                 $.ajax(orig2_url, {dataType: 'text', success: setValue});
@@ -95,13 +95,13 @@
         var val = e.currentTarget.checked;
         $('#compare').mergely('options', {ignorews: val});
         $('#compare').mergely('update');
-    })
+    });
     $('#edit_mode').change(function(e){
         var val = !e.currentTarget.checked;
         $('#compare').mergely('cm', 'lhs').setOption('readOnly', val);
         $('#compare').mergely('cm', 'rhs').setOption('readOnly', val);
         $('#compare').mergely('update');
-    })
+    });
 });
 </script>
 
--- a/kallithea/templates/files/files.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/files/files.html	Mon Jul 20 15:07:54 2015 +0200
@@ -64,10 +64,10 @@
         var url = el.href;
 
         var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
-        _base_url = _base_url.replace('//','/')
+        _base_url = _base_url.replace('//','/');
 
         //extract rev and the f_path from url.
-        parts = url.split(_base_url)
+        parts = url.split(_base_url);
         if(parts.length != 2){
             return false;
         }
@@ -145,7 +145,7 @@
     $('#hlcode').mouseup(getSelectionLink);
 
     // history select field
-    var cache = {}
+    var cache = {};
     $("#diff1").select2({
         placeholder: _TM['Select changeset'],
         dropdownAutoWidth: true,
@@ -160,10 +160,10 @@
                 var children = [];
                 $.each(this.children, function(){
                     if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
-                        children.push({'id': this.id, 'text': this.text})
+                        children.push({'id': this.id, 'text': this.text});
                     }
-                })
-                data.results.push({'text': section, 'children': children})
+                });
+                data.results.push({'text': section, 'children': children});
             });
             query.callback(data);
           }else{
@@ -176,7 +176,7 @@
                   cache[key] = data;
                   query.callback({results: data.results});
                 }
-              })
+              });
           }
         }
     });
@@ -186,10 +186,10 @@
             success: function(data) {
                 $('#file_authors').html(data);
                 $('#file_authors').show();
-                tooltip_activate()
+                tooltip_activate();
             }
-        })
-    })
+        });
+    });
 }
 
 $(document).ready(function(){
@@ -233,7 +233,8 @@
     // change branch filter
     $("#branch_selector").select2({
         dropdownAutoWidth: true,
-        minimumInputLength: 1
+        minimumInputLength: 1,
+        sortResults: branchSort
         });
 
     $("#branch_selector").change(function(e){
--- a/kallithea/templates/files/files_browser.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/files/files_browser.html	Mon Jul 20 15:07:54 2015 +0200
@@ -29,7 +29,7 @@
         <div class="browser-nav">
             ${h.form(h.url.current())}
             <div class="info_box">
-              <div class="info_box_elem rev">${_('revision')}</div>
+              <div class="info_box_elem rev">${_('Revision')}</div>
               <div class="info_box_elem"><a class="btn btn-mini ypjax-link" href="${c.url_prev}" title="${_('Previous revision')}"><i class="icon-left-open"></i></a></div>
               <div class="info_box_elem">${h.text('at_rev',value=c.changeset.revision,size=5)}</div>
               <div class="info_box_elem"><a class="btn btn-mini ypjax-link" href="${c.url_next}" title="${_('Next revision')}"><i class="icon-right-open"></i></a></div>
@@ -47,7 +47,7 @@
             <div>
                 <div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
                 <div id="node_filter_box" style="display:none">
-                ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}/<input class="init" type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
+                ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}/<input class="init" type="text" value="type to search..." name="filter" size="25" id="node_filter">
                 </div>
             </div>
         </div>
@@ -59,7 +59,6 @@
                 <tr>
                     <th>${_('Name')}</th>
                     <th>${_('Size')}</th>
-                    <th>${_('Mimetype')}</th>
                     <th>${_('Last Revision')}</th>
                     <th>${_('Last Modified')}</th>
                     <th>${_('Last Committer')}</th>
@@ -76,7 +75,6 @@
                     <td></td>
                     <td></td>
                     <td></td>
-                    <td></td>
                 </tr>
                 %endif
 
@@ -91,11 +89,6 @@
                      %endif
                      </td>
                      <td>
-                      %if node.is_file():
-                          ${node.mimetype}
-                      %endif
-                     </td>
-                     <td>
                          %if node.is_file():
                              <a title="${h.tooltip(node.last_changeset.message)}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=node.last_changeset.raw_id)}" class="tooltip revision-link">${h.show_id(node.last_changeset)}</a>
                          %endif
@@ -129,5 +122,5 @@
         if(search_GET == "1"){
             $("#filter_activate").click();
         }
-    })
+    });
 </script>
--- a/kallithea/templates/files/files_edit.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/files/files_edit.html	Mon Jul 20 15:07:54 2015 +0200
@@ -98,7 +98,7 @@
     var detected_mode = CodeMirror.findModeByExtension("${c.file.extension}");
     if(detected_mode){
         setCodeMirrorMode(myCodeMirror, detected_mode);
-        $($('#set_mode option[value="'+detected_mode.mime+'"]')[0]).attr("selected", "selected")
+        $($('#set_mode option[value="'+detected_mode.mime+'"]')[0]).attr("selected", "selected");
     }
 
     $('#set_mode').on('change', function(e){
@@ -107,6 +107,6 @@
         var detected_mode = CodeMirror.findModeByMIME(node.value);
         setCodeMirrorMode(myCodeMirror, detected_mode);
     });
-})
+});
 </script>
 </%def>
--- a/kallithea/templates/files/files_source.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/files/files_source.html	Mon Jul 20 15:07:54 2015 +0200
@@ -90,5 +90,5 @@
            }
         }
         callbacks(_State) // defined in files.html, main callbacks. Triggerd in pjax calls
-    })
+    });
 </script>
--- a/kallithea/templates/forks/fork.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/forks/fork.html	Mon Jul 20 15:07:54 2015 +0200
@@ -108,6 +108,6 @@
             'minimumResultsForSearch': -1
         });
         $('#repo_name').focus();
-    })
+    });
 </script>
 </%def>
--- a/kallithea/templates/index_base.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/index_base.html	Mon Jul 20 15:07:54 2015 +0200
@@ -59,11 +59,7 @@
                               </a>
                             </div>
                         </td>
-                        %if c.visual.stylify_metatags:
-                            <td>${h.urlify_text(h.desc_stylize(gr.group_description))}</td>
-                        %else:
-                            <td>${gr.group_description}</td>
-                        %endif
+                        <td>${h.urlify_text(gr.group_description, stylize=c.visual.stylify_metatags)}</td>
                         ## this is commented out since for multi nested repos can be HEAVY!
                         ## in number of executed queries during traversing uncomment at will
                         ##<td><b>${gr.repositories_recursive_count}</b></td>
@@ -112,7 +108,7 @@
             if (req) {
                 req = req.toLowerCase();
                 for (i = 0; i<data.length; i++) {
-                    var pos = data[i].raw_name.toLowerCase().indexOf(req, ${len(group_name)})
+                    var pos = data[i].raw_name.toLowerCase().indexOf(req, ${len(group_name)});
                     if (pos != -1) {
                         filtered.push(data[i]);
                     }
--- a/kallithea/templates/journal/journal.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/journal/journal.html	Mon Jul 20 15:07:54 2015 +0200
@@ -50,10 +50,10 @@
             </h5>
             <ul class="links nav nav-tabs">
                 <li class="active" id="show_watched_li">
-                    <a id="show_watched" href="#watched"><i class="icon-eye"></i> ${_('Watched')}</a>
+                    <a id="show_watched" href="#watched"><i class="icon-eye"></i> ${_('Watched Repositories')}</a>
                 </li>
                 <li id="show_my_li">
-                    <a id="show_my" href="#my"><i class="icon-database"></i> ${_('My Repos')}</a>
+                    <a id="show_my" href="#my"><i class="icon-database"></i> ${_('My Repositories')}</a>
                </li>
             </ul>
         </div>
@@ -172,7 +172,7 @@
             if (req) {
                 req = req.toLowerCase();
                 for (i = 0; i<data.length; i++) {
-                    var pos = data[i].raw_name.toLowerCase().indexOf(req)
+                    var pos = data[i].raw_name.toLowerCase().indexOf(req);
                     if (pos != -1) {
                         filtered.push(data[i]);
                     }
@@ -263,7 +263,7 @@
             if (req) {
                 req = req.toLowerCase();
                 for (i = 0; i<data.length; i++) {
-                    var pos = data[i].raw_name.toLowerCase().indexOf(req)
+                    var pos = data[i].raw_name.toLowerCase().indexOf(req);
                     if (pos != -1) {
                         filtered.push(data[i]);
                     }
--- a/kallithea/templates/login.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/login.html	Mon Jul 20 15:07:54 2015 +0200
@@ -5,50 +5,47 @@
     ${_('Log In')}
 </%block>
 
-<div id="login">
+<div id="login" class="panel panel-default">
     <%include file="/base/flash_msg.html"/>
     <!-- login -->
-    <div class="title withlogo">
+    <div class="panel-heading title withlogo">
         %if c.site_name:
             <h5>${_('Log In to %s') % c.site_name}</h5>
         %else:
             <h5>${_('Log In')}</h5>
         %endif
     </div>
-    <div class="inner">
-        ${h.form(h.url.current(came_from=c.came_from))}
+    <div class="panel-body inner">
+        ${h.form(h.url.current(**request.GET))}
         <div class="form">
             <i class="icon-lock"></i>
             <!-- fields -->
 
-            <div class="fields">
-                <div class="field">
-                    <div class="label">
-                        <label for="username">${_('Username')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.text('username',class_='focus large')}
+            <div class="form-horizontal">
+                <div class="form-group">
+                    <label class="control-label col-sm-5" for="username">${_('Username')}:</label>
+                    <div class="input col-sm-7">
+                        ${h.text('username',class_='form-control focus large')}
                     </div>
 
                 </div>
-                <div class="field">
-                    <div class="label">
-                        <label for="password">${_('Password')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.password('password',class_='focus large')}
+                <div class="form-group">
+                    <label class="control-label col-sm-5" for="password">${_('Password')}:</label>
+                    <div class="input col-sm-7">
+                        ${h.password('password',class_='form-control focus large')}
                     </div>
 
                 </div>
-                <div class="field">
-                    <div class="checkbox">
-                        <input type="checkbox" id="remember" name="remember" />
-                        <label for="remember">${_('Remember me')}</label>
+                <div class="form-group">
+                    <div class="col-sm-offset-5 col-sm-7">
+                        <div class="checkbox">
+                            <label for="remember">
+                                <input type="checkbox" id="remember" name="remember"/>
+                                ${_('Remember me')}
+                            </label>
+                        </div>
                     </div>
                 </div>
-                <div class="buttons">
-                    ${h.submit('sign_in',_('Sign In'),class_="btn")}
-                </div>
             </div>
             <!-- end fields -->
             <!-- links -->
@@ -58,6 +55,9 @@
                   /
                  ${h.link_to(_("Don't have an account ?"),h.url('register'))}
                 %endif
+                <span class="buttons">
+                    ${h.submit('sign_in',_('Sign In'),class_="btn btn-default")}
+                </span>
             </div>
 
             <!-- end links -->
--- a/kallithea/templates/pullrequests/pullrequest.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/pullrequests/pullrequest.html	Mon Jul 20 15:07:54 2015 +0200
@@ -130,7 +130,7 @@
 <script type="text/javascript">
   var _USERS_AC_DATA = ${c.users_array|n};
   var _GROUPS_AC_DATA = ${c.user_groups_array|n};
-  PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
+  PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA);
 
   pyroutes.register('pullrequest_repo_info', "${url('pullrequest_repo_info',repo_name='%(repo_name)s')}", ['repo_name']);
 
@@ -224,7 +224,8 @@
       ## (org_repo can't change)
 
       $("#org_ref").select2({
-          dropdownAutoWidth: true
+          dropdownAutoWidth: true,
+          sortResults: branchSort
       });
       $("#org_ref").on("change", function(e){
           loadPreview();
@@ -238,7 +239,8 @@
       });
 
       $("#other_ref").select2({
-          dropdownAutoWidth: true
+          dropdownAutoWidth: true,
+          sortResults: branchSort
       });
       $("#other_ref").on("change", function(e){
           loadPreview();
--- a/kallithea/templates/pullrequests/pullrequest_data.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/pullrequests/pullrequest_data.html	Mon Jul 20 15:07:54 2015 +0200
@@ -58,7 +58,7 @@
         </a>
       </td>
       <td>
-        ${pr.author.username_and_name}
+        ${pr.author.full_name_and_username}
       </td>
       <td>
         <span class="tooltip" title="${h.tooltip(h.fmt_date(pr.created_on))}">
--- a/kallithea/templates/pullrequests/pullrequest_show.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/pullrequests/pullrequest_show.html	Mon Jul 20 15:07:54 2015 +0200
@@ -3,11 +3,11 @@
 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
 <%block name="title">
-    ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
+    ${_('%s Pull Request %s') % (c.repo_name, c.pull_request.nice_id())}
 </%block>
 
 <%def name="breadcrumbs_links()">
-    ${_('Pull request #%s from %s#%s') % (c.pull_request.pull_request_id, c.pull_request.org_repo.repo_name, c.cs_branch_name)}
+    ${_('Pull request %s from %s#%s') % (c.pull_request.nice_id(), c.pull_request.org_repo.repo_name, c.cs_branch_name)}
 </%def>
 
 <%block name="header_menu">
@@ -38,7 +38,7 @@
             <label>${_('Description')}:</label>
             %if editable:
             <div style="margin: 5px">
-              <a class="btn btn-small" onclick="YUD.setStyle('pr-edit-form','display','');YUD.setStyle(YUD.getElementsByClassName('pr-not-edit'),'display','none')">${_("Edit")}</a>
+              <a class="btn btn-small" onclick="$('#pr-edit-form').show();$('.pr-not-edit').hide()">${_("Edit")}</a>
             </div>
             %endif
           </div>
@@ -76,15 +76,14 @@
           <div class="input">
             <div class="changeset-status-container" style="float:none;clear:both">
             %if c.current_voting_result:
-              <div class="changeset-status-ico" style="padding:0px 4px 0px 0px">
-                  <i class="icon-circle changeset-status-${c.current_voting_result}" title="${_('Pull request status calculated from votes')}"></i></div>
-              <div class="changeset-status-lbl tooltip" title="${_('Pull request status calculated from votes')}">
+              <span class="changeset-status-ico" style="padding:0px 4px 0px 0px">
+                  <i class="icon-circle changeset-status-${c.current_voting_result}" title="${_('Pull request status calculated from votes')}"></i></span>
+              <span class="changeset-status-lbl tooltip" title="${_('Pull request status calculated from votes')}">
                 %if c.pull_request.is_closed():
                     ${_('Closed')},
                 %endif
                 ${h.changeset_status_lbl(c.current_voting_result)}
-              </div>
-
+              </span>
             %endif
             </div>
           </div>
@@ -159,7 +158,7 @@
                   <div class="gravatar">
                     ${h.gravatar(c.pull_request.author.email, size=20)}
                   </div>
-                  <span>${c.pull_request.author.username_and_name}</span><br/>
+                  <span>${c.pull_request.author.full_name_and_username}</span><br/>
                   <span><a href="mailto:${c.pull_request.author.email}">${c.pull_request.author.email}</a></span><br/>
               </div>
           </div>
@@ -213,6 +212,9 @@
           <div>
             <ul id="review_members" class="group_members">
             %for member,status in c.pull_request_reviewers:
+              ## WARNING: the HTML below is duplicate with
+              ## kallithea/public/js/base.js
+              ## If you change something here it should be reflected in the template too.
               <li id="reviewer_${member.user_id}">
                 <div class="reviewers_member">
                     <div class="reviewer_status tooltip" title="${h.tooltip(h.changeset_status_lbl(status.status if status else 'not_reviewed'))}">
@@ -221,7 +223,7 @@
                   <div class="reviewer_gravatar gravatar">
                     ${h.gravatar(member.email, size=14)}
                   </div>
-                  <div style="float:left;">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
+                  <div style="float:left;">${member.full_name_and_username} (${_('Owner') if c.pull_request.user_id == member.user_id else _('Reviewer')})</div>
                   <input type="hidden" value="${member.user_id}" name="review_members" />
                   %if editable:
                   <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id})" title="${_('Remove reviewer')}">
@@ -342,6 +344,7 @@
     </script>
 
     ## diff block
+    <div class="commentable-diff">
     <%namespace name="diff_block" file="/changeset/diff_block.html"/>
     ${diff_block.diff_block_js()}
     %for fid, change, f, stat in c.files:
@@ -350,7 +353,7 @@
     % if c.limited_diff:
       <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff anyway')}</a></h4>
     % endif
-
+    </div>
 
     ## template for inline comment form
     ${comment.comment_inline_form()}
@@ -366,7 +369,7 @@
 
     <script type="text/javascript">
       $(document).ready(function(){
-          PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
+          PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA);
 
           $('.add-bubble').click(function(e){
               var tr = e.currentTarget;
@@ -381,7 +384,7 @@
           var file_comments = $('.inline-comment-placeholder').toArray();
           renderInlineComments(file_comments);
 
-          linkInlineComments(document.getElementsByClassName('firstlink'), document.getElementsByClassName("comment"));
+          linkInlineComments($('.firstlink'), $('.comment'));
 
           $('#updaterevs input').change(function(e){
               var update = !!e.target.value;
--- a/kallithea/templates/register.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/register.html	Mon Jul 20 15:07:54 2015 +0200
@@ -33,7 +33,7 @@
                         <label for="password">${_('Password')}:</label>
                     </div>
                     <div class="input">
-                        ${h.password('password',class_="medium",autocomplete="off")}
+                        ${h.password('password',class_="medium")}
                     </div>
                 </div>
 
@@ -42,7 +42,7 @@
                         <label for="password">${_('Re-enter password')}:</label>
                     </div>
                     <div class="input">
-                        ${h.password('password_confirmation',class_="medium",autocomplete="off")}
+                        ${h.password('password_confirmation',class_="medium")}
                     </div>
                 </div>
 
--- a/kallithea/templates/search/search.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/search/search.html	Mon Jul 20 15:07:54 2015 +0200
@@ -64,8 +64,8 @@
                 <div class="select">
                     ${h.select('type',c.cur_type,[('content',_('File contents')),
                         ('commit',_('Commit messages')),
-                        ('path',_('File names'))
-                        ##('repository',_('Repository names'))
+                        ('path',_('File names')),
+                        ##('repository',_('Repository names')),
                         ])}
                 </div>
              </div>
--- a/kallithea/templates/summary/statistics.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/summary/statistics.html	Mon Jul 20 15:07:54 2015 +0200
@@ -379,7 +379,7 @@
                     item.series.label = 'commits';
                 }
                 var d = new Date(x*1000);
-                var fd = d.toDateString()
+                var fd = d.toDateString();
                 var nr_commits = parseInt(y);
 
                 var cur_data = dataset[item.series.label].data[item.dataIndex];
@@ -433,7 +433,7 @@
     overview.setSelection(initial_ranges);
 
     plot.subscribe("plotselected", plotselected);
-    plot.subscribe("plothover", plothover)
+    plot.subscribe("plothover", plothover);
 
     overview.subscribe("plotselected", function (ranges) {
         plot.setSelection(ranges);
--- a/kallithea/templates/summary/summary.html	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/templates/summary/summary.html	Mon Jul 20 15:07:54 2015 +0200
@@ -84,11 +84,7 @@
               <div class="label-summary">
                   <label>${_('Description')}:</label>
               </div>
-                 %if c.visual.stylify_metatags:
-                   <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(h.html_escape(c.db_repo.description)))}</div>
-                 %else:
-                   <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.html_escape(c.db_repo.description))}</div>
-                 %endif
+              <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.db_repo.description, stylize=c.visual.stylify_metatags)}</div>
             </div>
 
             <div class="field">
@@ -126,7 +122,7 @@
                     ${h.hidden('download_options')}
                     <span style="vertical-align: bottom">
                       <input id="archive_subrepos" type="checkbox" name="subrepos" />
-                      <label for="archive_subrepos" class="tooltip" title="${h.tooltip(_('Check this to download archive with subrepos'))}" >${_('with subrepos')}</label>
+                      <label for="archive_subrepos" class="tooltip" title="${h.tooltip(_('Check this to download archive with subrepos'))}" >${_('With subrepos')}</label>
                     </span>
                 %endif
               </div>
@@ -267,10 +263,10 @@
                 var children = [];
                 $.each(this.children, function(){
                     if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
-                        children.push({'id': this.id, 'text': this.text})
+                        children.push({'id': this.id, 'text': this.text});
                     }
-                })
-                data.results.push({'text': section, 'children': children})
+                });
+                data.results.push({'text': section, 'children': children});
             });
             query.callback(data);
           }else{
@@ -283,7 +279,7 @@
                   cache[key] = data;
                   query.callback({results: data.results});
                 }
-              })
+              });
           }
         }
     });
@@ -303,7 +299,7 @@
              url = url.replace('__SUB__',subrepos);
              url = url.replace('__NAME__',title_tmpl);
 
-             s.html(url)
+             s.html(url);
            }
        }
     });
@@ -312,7 +308,7 @@
     %for cnt,archive in enumerate(c.db_repo_scm_instance._get_archives()):
       tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.db_repo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='btn btn-small')}';
     %endfor
-})
+});
 </script>
 
 %if c.show_stats:
--- a/kallithea/tests/__init__.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/__init__.py	Mon Jul 20 15:07:54 2015 +0200
@@ -54,8 +54,9 @@
 
 from kallithea.lib.compat import unittest
 from kallithea import is_windows
-from kallithea.model.db import User
-from kallithea.tests.nose_parametrized import parameterized
+from kallithea.model.db import Notification, User, UserNotification
+from kallithea.model.meta import Session
+from kallithea.tests.parameterized import parameterized
 from kallithea.lib.utils2 import safe_str
 
 
@@ -70,7 +71,7 @@
     'SkipTest', 'ldap_lib_installed', 'BaseTestCase', 'init_stack',
     'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO',
     'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
-    'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
+    'TEST_USER_ADMIN_EMAIL', 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
     'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
     'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
     'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
@@ -183,6 +184,15 @@
         init_stack(self.wsgiapp.config)
         unittest.TestCase.__init__(self, *args, **kwargs)
 
+    def remove_all_notifications(self):
+        Notification.query().delete()
+
+        # Because query().delete() does not (by default) trigger cascades.
+        # http://docs.sqlalchemy.org/en/rel_0_7/orm/collections.html#passive-deletes
+        UserNotification.query().delete()
+
+        Session().commit()
+
 
 class TestController(BaseTestCase):
 
--- a/kallithea/tests/api/api_base.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/api/api_base.py	Mon Jul 20 15:07:54 2015 +0200
@@ -88,7 +88,7 @@
     return gr
 
 
-class BaseTestApi(object):
+class _BaseTestApi(object):
     REPO = None
     REPO_TYPE = None
 
@@ -166,7 +166,7 @@
         id_, params = _build_data('trololo', 'get_user')
         response = api_call(self, params)
 
-        expected = 'Invalid API KEY'
+        expected = 'Invalid API key'
         self._compare_error(id_, expected, given=response.body)
 
     def test_api_missing_non_optional_param(self):
--- a/kallithea/tests/api/test_api_git.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/api/test_api_git.py	Mon Jul 20 15:07:54 2015 +0200
@@ -12,10 +12,10 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from kallithea.tests import *
-from kallithea.tests.api.api_base import BaseTestApi
+from kallithea.tests import TestController, GIT_REPO
+from kallithea.tests.api.api_base import _BaseTestApi
 
 
-class TestGitApi(BaseTestApi, TestController):
+class TestGitApi(_BaseTestApi, TestController):
     REPO = GIT_REPO
     REPO_TYPE = 'git'
--- a/kallithea/tests/api/test_api_hg.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/api/test_api_hg.py	Mon Jul 20 15:07:54 2015 +0200
@@ -12,10 +12,10 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from kallithea.tests import *
-from kallithea.tests.api.api_base import BaseTestApi
+from kallithea.tests import TestController, HG_REPO
+from kallithea.tests.api.api_base import _BaseTestApi
 
 
-class TestHgApi(BaseTestApi, TestController):
+class TestHgApi(_BaseTestApi, TestController):
     REPO = HG_REPO
     REPO_TYPE = 'hg'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/conftest.py	Mon Jul 20 15:07:54 2015 +0200
@@ -0,0 +1,34 @@
+import os
+import sys
+import logging
+
+import pkg_resources
+from paste.deploy import loadapp
+import pylons.test
+from pylons.i18n.translation import _get_translator
+
+
+def pytest_configure():
+    path = os.getcwd()
+    sys.path.insert(0, path)
+    pkg_resources.working_set.add_entry(path)
+
+    # Disable INFO logging of test database creation, restore with NOTSET
+    logging.disable(logging.INFO)
+    pylons.test.pylonsapp = loadapp('config:test.ini', relative_to=path)
+    logging.disable(logging.NOTSET)
+
+    # Setup the config and app_globals, only works if we can get
+    # to the config object
+    conf = getattr(pylons.test.pylonsapp, 'config')
+    if conf:
+        pylons.config._push_object(conf)
+
+        if 'pylons.app_globals' in conf:
+            pylons.app_globals._push_object(conf['pylons.app_globals'])
+
+    # Initialize a translator for tests that utilize i18n
+    translator = _get_translator(pylons.config.get('lang'))
+    pylons.translator._push_object(translator)
+
+    return pylons.test.pylonsapp
--- a/kallithea/tests/fixtures/journal_dump.csv	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/fixtures/journal_dump.csv	Mon Jul 20 15:07:54 2015 +0200
@@ -1782,9 +1782,9 @@
 1846,3,demo,99,another-fork-to-check-code-review,"",user_commented_revision:fba17a64fa4978bfea19222da5e64a18cfddeecd,2012-11-06 04:15:29.032768
 1847,3,demo,68,aaa-project,"",user_commented_revision:0c33fa58efc5a5541d8d3f1a3d3b77367e3d94f5,2012-11-06 04:17:16.091927
 1848,3,demo,249,abcdefg,"",user_commented_revision:5d80e28538141e322b317168e2367fb03178d58c,2012-11-06 06:10:53.372505
-1849,3,demo,362,Marcin,"",started_following_repo,2012-11-06 10:45:38.505485
-1850,3,demo,362,Marcin,"",user_created_repo,2012-11-06 10:45:38.518969
-1851,3,demo,362,hidden/Marcin,"",user_updated_repo,2012-11-06 10:45:46.281581
+1849,3,demo,362,Username,"",started_following_repo,2012-11-06 10:45:38.505485
+1850,3,demo,362,Username,"",user_created_repo,2012-11-06 10:45:38.518969
+1851,3,demo,362,hidden/Username,"",user_updated_repo,2012-11-06 10:45:46.281581
 1852,3,demo,177,blah,62.200.22.2,push:b369fb18c8d61fe0d3b14c417466680230cabe46,2012-11-06 10:47:55.655029
 1853,3,demo,99,another-fork-to-check-code-review,"",user_commented_revision:d5422faf648cc589425cd3b0dbf1f6dbf93036a0,2012-11-06 13:12:05.517155
 1854,3,demo,38,code-review-test,"",user_commented_revision:6d7db5794e8cad7da042b6ae6238116c6e59a4d2,2012-11-06 16:12:59.38977
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/fixtures/markuptest.diff	Mon Jul 20 15:07:54 2015 +0200
@@ -0,0 +1,17 @@
+diff --git a/f b/f
+--- a/f	
++++ b/f	
+@@ -51,5 +51,12 @@
+ 	begin();
+ 	
++	int foo;
++	int bar; 
++	int baz;	
++	int space; 
++	int tab;	
++	
+  
+-	#define MAX_STEPS (48)
++	
++	#define MAX_STEPS (64)
+ 
--- a/kallithea/tests/functional/test_admin_auth_settings.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_admin_auth_settings.py	Mon Jul 20 15:07:54 2015 +0200
@@ -111,3 +111,79 @@
 
     def test_ldap_login_incorrect(self):
         pass
+
+    def _container_auth_setup(self, **settings):
+        self.log_user()
+
+        params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_container')
+        params.update(settings)
+
+        test_url = url(controller='admin/auth_settings',
+                       action='auth_settings')
+
+        response = self.app.post(url=test_url, params=params)
+        response = response.follow()
+        response.click('Log Out') # end admin login session
+
+    def _container_auth_verify_login(self, resulting_username, **get_kwargs):
+        response = self.app.get(
+            url=url(controller='admin/my_account', action='my_account'),
+            **get_kwargs
+        )
+        response.mustcontain('My Account %s' % resulting_username)
+
+    def test_container_auth_login_header(self):
+        self._container_auth_setup(
+            auth_container_header='THE_USER_NAME',
+            auth_container_fallback_header='',
+            auth_container_clean_username='False',
+        )
+        self._container_auth_verify_login(
+            extra_environ={'THE_USER_NAME': 'john@example.org'},
+            resulting_username='john@example.org',
+        )
+
+    def test_container_auth_login_fallback_header(self):
+        self._container_auth_setup(
+            auth_container_header='THE_USER_NAME',
+            auth_container_fallback_header='HTTP_X_YZZY',
+            auth_container_clean_username='False',
+        )
+        self._container_auth_verify_login(
+            headers={'X-Yzzy': r'foo\bar'},
+            resulting_username=r'foo\bar',
+        )
+
+    def test_container_auth_clean_username_at(self):
+        self._container_auth_setup(
+            auth_container_header='REMOTE_USER',
+            auth_container_fallback_header='',
+            auth_container_clean_username='True',
+        )
+        self._container_auth_verify_login(
+            extra_environ={'REMOTE_USER': 'john@example.org'},
+            resulting_username='john',
+        )
+
+    def test_container_auth_clean_username_backslash(self):
+        self._container_auth_setup(
+            auth_container_header='REMOTE_USER',
+            auth_container_fallback_header='',
+            auth_container_clean_username='True',
+        )
+        self._container_auth_verify_login(
+            extra_environ={'REMOTE_USER': r'example\jane'},
+            resulting_username=r'jane',
+        )
+
+    def test_container_auth_no_logout(self):
+        self._container_auth_setup(
+            auth_container_header='REMOTE_USER',
+            auth_container_fallback_header='',
+            auth_container_clean_username='True',
+        )
+        response = self.app.get(
+            url=url(controller='admin/my_account', action='my_account'),
+            extra_environ={'REMOTE_USER': 'john'},
+        )
+        self.assertNotIn('Log Out', response.normal_body)
--- a/kallithea/tests/functional/test_admin_gists.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_admin_gists.py	Mon Jul 20 15:07:54 2015 +0200
@@ -154,7 +154,7 @@
         gist = _create_gist('gist-show-me')
         response = self.app.get(url('gist', gist_id=gist.gist_access_id))
         response.mustcontain('added file: gist-show-me<')
-        response.mustcontain('test_admin - created')
+        response.mustcontain('%s - created' % TEST_USER_ADMIN_LOGIN)
         response.mustcontain('gist-desc')
         response.mustcontain('<div class="btn btn-mini btn-success disabled">Public Gist</div>')
 
--- a/kallithea/tests/functional/test_admin_notifications.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_admin_notifications.py	Mon Jul 20 15:07:54 2015 +0200
@@ -4,15 +4,12 @@
 from kallithea.model.user import UserModel
 from kallithea.model.notification import NotificationModel
 from kallithea.model.meta import Session
+from kallithea.lib import helpers as h
 
 
 class TestNotificationsController(TestController):
-
-    def tearDown(self):
-        for n in Notification.query().all():
-            inst = Notification.get(n.notification_id)
-            Session().delete(inst)
-        Session().commit()
+    def setUp(self):
+        self.remove_all_notifications()
 
     def test_index(self):
         self.log_user()
@@ -89,3 +86,39 @@
 
         response.mustcontain(subject)
         response.mustcontain(notif_body)
+
+    def test_description_with_age(self):
+        self.log_user()
+        cur_user = self._get_logged_user()
+        subject = u'test'
+        notify_body = u'hi there'
+        notification = NotificationModel().create(created_by = cur_user,
+                                                  subject    = subject,
+                                                  body       = notify_body)
+
+        description = NotificationModel().make_description(notification)
+        self.assertEqual(
+            description,
+            "{0} sent message {1}".format(
+                cur_user.username,
+                h.age(notification.created_on)
+                )
+            )
+
+    def test_description_with_datetime(self):
+        self.log_user()
+        cur_user = self._get_logged_user()
+        subject = u'test'
+        notify_body = u'hi there'
+        notification = NotificationModel().create(created_by = cur_user,
+                                                  subject    = subject,
+                                                  body       = notify_body)
+
+        description = NotificationModel().make_description(notification, False)
+        self.assertEqual(
+            description,
+            "{0} sent message at {1}".format(
+                cur_user.username,
+                h.fmt_date(notification.created_on)
+                )
+            )
--- a/kallithea/tests/functional/test_admin_repos.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_admin_repos.py	Mon Jul 20 15:07:54 2015 +0200
@@ -26,7 +26,7 @@
     return perm
 
 
-class _BaseTest(TestController):
+class _BaseTest(object):
     """
     Write all tests here
     """
@@ -348,7 +348,7 @@
                                                 repo_description=description,
                                                 clone_uri='http://127.0.0.1/repo',
                                                 _authentication_token=self.authentication_token()))
-        response.mustcontain('invalid clone URL')
+        response.mustcontain('Invalid repository URL')
 
 
     def test_create_remote_repo_wrong_clone_uri_hg_svn(self):
@@ -362,7 +362,7 @@
                                                 repo_description=description,
                                                 clone_uri='svn+http://127.0.0.1/repo',
                                                 _authentication_token=self.authentication_token()))
-        response.mustcontain('invalid clone URL')
+        response.mustcontain('Invalid repository URL')
 
 
     def test_delete(self):
@@ -525,7 +525,7 @@
         self.log_user()
         repo = Repository.get_by_repo_name(self.REPO)
         response = self.app.get(url('edit_repo_advanced', repo_name=self.REPO))
-        opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
+        opt = """<option value="%s">%s</option>""" % (repo.repo_id, self.REPO)
         response.mustcontain(no=[opt])
 
     def test_set_fork_of_other_repo(self):
@@ -638,7 +638,7 @@
         # repo must not be in filesystem !
         self.assertFalse(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)))
 
-class TestAdminReposControllerGIT(_BaseTest):
+class TestAdminReposControllerGIT(TestController, _BaseTest):
     REPO = GIT_REPO
     REPO_TYPE = 'git'
     NEW_REPO = NEW_GIT_REPO
@@ -646,7 +646,7 @@
     OTHER_TYPE = 'hg'
 
 
-class TestAdminReposControllerHG(_BaseTest):
+class TestAdminReposControllerHG(TestController, _BaseTest):
     REPO = HG_REPO
     REPO_TYPE = 'hg'
     NEW_REPO = NEW_HG_REPO
--- a/kallithea/tests/functional/test_admin_users.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_admin_users.py	Mon Jul 20 15:07:54 2015 +0200
@@ -13,6 +13,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sqlalchemy.orm.exc import NoResultFound
+from webob.exc import HTTPNotFound
 
 from kallithea.tests import *
 from kallithea.tests.fixture import Fixture
@@ -395,7 +396,7 @@
                                 params=dict(new_ip=ip, _authentication_token=self.authentication_token()))
 
         if failure:
-            self.checkSessionFlash(response, 'Please enter a valid IPv4 or IpV6 address')
+            self.checkSessionFlash(response, 'Please enter a valid IPv4 or IPv6 address')
             response = self.app.get(url('edit_user_ips', id=user_id))
             response.mustcontain(no=[ip])
             response.mustcontain(no=[ip_range])
@@ -438,7 +439,7 @@
         user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
         response = self.app.get(url('edit_user_api_keys', id=user.user_id))
         response.mustcontain(user.api_key)
-        response.mustcontain('expires: never')
+        response.mustcontain('Expires: Never')
 
     @parameterized.expand([
         ('forever', -1),
@@ -451,8 +452,8 @@
         user_id = user.user_id
 
         response = self.app.post(url('edit_user_api_keys', id=user_id),
-                 {'_method': 'put', 'description': desc, 'lifetime': lifetime, '_authentication_token': self.authentication_token()})
-        self.checkSessionFlash(response, 'Api key successfully created')
+                 {'description': desc, 'lifetime': lifetime, '_authentication_token': self.authentication_token()})
+        self.checkSessionFlash(response, 'API key successfully created')
         try:
             response = response.follow()
             user = User.get(user_id)
@@ -469,8 +470,8 @@
         user_id = user.user_id
 
         response = self.app.post(url('edit_user_api_keys', id=user_id),
-                {'_method': 'put', 'description': 'desc', 'lifetime': -1, '_authentication_token': self.authentication_token()})
-        self.checkSessionFlash(response, 'Api key successfully created')
+                {'description': 'desc', 'lifetime': -1, '_authentication_token': self.authentication_token()})
+        self.checkSessionFlash(response, 'API key successfully created')
         response = response.follow()
 
         #now delete our key
@@ -479,7 +480,7 @@
 
         response = self.app.post(url('edit_user_api_keys', id=user_id),
                  {'_method': 'delete', 'del_api_key': keys[0].api_key, '_authentication_token': self.authentication_token()})
-        self.checkSessionFlash(response, 'Api key successfully deleted')
+        self.checkSessionFlash(response, 'API key successfully deleted')
         keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
         self.assertEqual(0, len(keys))
 
@@ -490,10 +491,106 @@
         api_key = user.api_key
         response = self.app.get(url('edit_user_api_keys', id=user_id))
         response.mustcontain(api_key)
-        response.mustcontain('expires: never')
+        response.mustcontain('Expires: Never')
 
         response = self.app.post(url('edit_user_api_keys', id=user_id),
                  {'_method': 'delete', 'del_api_key_builtin': api_key, '_authentication_token': self.authentication_token()})
-        self.checkSessionFlash(response, 'Api key successfully reset')
+        self.checkSessionFlash(response, 'API key successfully reset')
         response = response.follow()
         response.mustcontain(no=[api_key])
+
+# TODO To be uncommented when pytest is the test runner
+#import pytest
+#from kallithea.controllers.admin.users import UsersController
+#class TestAdminUsersController_unittest(object):
+#    """
+#    Unit tests for the users controller
+#    These are in a separate class, not deriving from TestController (and thus
+#    unittest.TestCase), to be able to benefit from pytest features like
+#    monkeypatch.
+#    """
+#    def test_get_user_or_raise_if_default(self, monkeypatch):
+#        # flash complains about an unexisting session
+#        def flash_mock(*args, **kwargs):
+#            pass
+#        monkeypatch.setattr(h, 'flash', flash_mock)
+#
+#        u = UsersController()
+#        # a regular user should work correctly
+#        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+#        assert u._get_user_or_raise_if_default(user.user_id) == user
+#        # the default user should raise
+#        with pytest.raises(HTTPNotFound):
+#            u._get_user_or_raise_if_default(User.get_default_user().user_id)
+
+
+class TestAdminUsersControllerForDefaultUser(TestController):
+    """
+    Edit actions on the default user are not allowed.
+    Validate that they throw a 404 exception.
+    """
+    def test_edit_default_user(self):
+        self.log_user()
+        user = User.get_default_user()
+        response = self.app.get(url('edit_user', id=user.user_id), status=404)
+
+    def test_edit_advanced_default_user(self):
+        self.log_user()
+        user = User.get_default_user()
+        response = self.app.get(url('edit_user_advanced', id=user.user_id), status=404)
+
+    # API keys
+    def test_edit_api_keys_default_user(self):
+        self.log_user()
+        user = User.get_default_user()
+        response = self.app.get(url('edit_user_api_keys', id=user.user_id), status=404)
+
+    def test_add_api_keys_default_user(self):
+        self.log_user()
+        user = User.get_default_user()
+        response = self.app.post(url('edit_user_api_keys', id=user.user_id),
+                 {'_method': 'put', '_authentication_token': self.authentication_token()}, status=404)
+
+    def test_delete_api_keys_default_user(self):
+        self.log_user()
+        user = User.get_default_user()
+        response = self.app.post(url('edit_user_api_keys', id=user.user_id),
+                 {'_method': 'delete', '_authentication_token': self.authentication_token()}, status=404)
+
+    # Permissions
+    def test_edit_perms_default_user(self):
+        self.log_user()
+        user = User.get_default_user()
+        response = self.app.get(url('edit_user_perms', id=user.user_id), status=404)
+
+    def test_update_perms_default_user(self):
+        self.log_user()
+        user = User.get_default_user()
+        response = self.app.post(url('edit_user_perms', id=user.user_id),
+                 {'_method': 'put', '_authentication_token': self.authentication_token()}, status=404)
+
+    # E-mails
+    def test_edit_emails_default_user(self):
+        self.log_user()
+        user = User.get_default_user()
+        response = self.app.get(url('edit_user_emails', id=user.user_id), status=404)
+
+    def test_add_emails_default_user(self):
+        self.log_user()
+        user = User.get_default_user()
+        response = self.app.post(url('edit_user_emails', id=user.user_id),
+                 {'_method': 'put', '_authentication_token': self.authentication_token()}, status=404)
+
+    def test_delete_emails_default_user(self):
+        self.log_user()
+        user = User.get_default_user()
+        response = self.app.post(url('edit_user_emails', id=user.user_id),
+                 {'_method': 'delete', '_authentication_token': self.authentication_token()}, status=404)
+
+    # IP addresses
+    # Add/delete of IP addresses for the default user is used to maintain
+    # the global IP whitelist and thus allowed. Only 'edit' is forbidden.
+    def test_edit_ip_default_user(self):
+        self.log_user()
+        user = User.get_default_user()
+        response = self.app.get(url('edit_user_ips', id=user.user_id), status=404)
--- a/kallithea/tests/functional/test_changeset_comments.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_changeset_comments.py	Mon Jul 20 15:07:54 2015 +0200
@@ -11,18 +11,7 @@
             Session().delete(x)
         Session().commit()
 
-        for x in Notification.query().all():
-            Session().delete(x)
-        Session().commit()
-
-    def tearDown(self):
-        for x in ChangesetComment.query().all():
-            Session().delete(x)
-        Session().commit()
-
-        for x in Notification.query().all():
-            Session().delete(x)
-        Session().commit()
+        self.remove_all_notifications()
 
     def test_create(self):
         self.log_user()
@@ -54,8 +43,8 @@
         ID = ChangesetComment.query().first().comment_id
         self.assertEqual(notification.type_,
                          Notification.TYPE_CHANGESET_COMMENT)
-        sbj = (u'/vcs_test_hg/changeset/'
-               '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % ID)
+        sbj = (u'/%s/changeset/'
+               '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % (HG_REPO, ID))
         print "%s vs %s" % (sbj, notification.subject)
         self.assertTrue(sbj in notification.subject)
 
@@ -95,8 +84,8 @@
         ID = ChangesetComment.query().first().comment_id
         self.assertEqual(notification.type_,
                          Notification.TYPE_CHANGESET_COMMENT)
-        sbj = (u'/vcs_test_hg/changeset/'
-               '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % ID)
+        sbj = (u'/%s/changeset/'
+               '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % (HG_REPO, ID))
         print "%s vs %s" % (sbj, notification.subject)
         self.assertTrue(sbj in notification.subject)
 
@@ -104,7 +93,7 @@
         self.log_user()
 
         rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
-        text = u'@test_regular check CommentOnRevision'
+        text = u'@%s check CommentOnRevision' % TEST_USER_REGULAR_LOGIN
 
         params = {'text': text, '_authentication_token': self.authentication_token()}
         response = self.app.post(url(controller='changeset', action='comment',
@@ -127,7 +116,7 @@
         users = [x.user.username for x in UserNotification.query().all()]
 
         # test_regular gets notification by @mention
-        self.assertEqual(sorted(users), [u'test_admin', u'test_regular'])
+        self.assertEqual(sorted(users), [TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN])
 
     def test_delete(self):
         self.log_user()
--- a/kallithea/tests/functional/test_files.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_files.py	Mon Jul 20 15:07:54 2015 +0200
@@ -33,19 +33,19 @@
                                     revision='tip',
                                     f_path='/'))
         # Test response...
-        response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/docs"><i class="icon-folder-open"></i><span>docs</span></a>')
-        response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/vcs"><i class="icon-folder-open"></i><span>vcs</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.gitignore"><i class="icon-doc"></i><span>.gitignore</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.hgignore"><i class="icon-doc"></i><span>.hgignore</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.hgtags"><i class="icon-doc"></i><span>.hgtags</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.travis.yml"><i class="icon-doc"></i><span>.travis.yml</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/MANIFEST.in"><i class="icon-doc"></i><span>MANIFEST.in</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/README.rst"><i class="icon-doc"></i><span>README.rst</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/run_test_and_report.sh"><i class="icon-doc"></i><span>run_test_and_report.sh</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/setup.cfg"><i class="icon-doc"></i><span>setup.cfg</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/setup.py"><i class="icon-doc"></i><span>setup.py</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/test_and_report.sh"><i class="icon-doc"></i><span>test_and_report.sh</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/tox.ini"><i class="icon-doc"></i><span>tox.ini</span></a>')
+        response.mustcontain('<a class="browser-dir ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/docs"><i class="icon-folder-open"></i><span>docs</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-dir ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/vcs"><i class="icon-folder-open"></i><span>vcs</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.gitignore"><i class="icon-doc"></i><span>.gitignore</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.hgignore"><i class="icon-doc"></i><span>.hgignore</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.hgtags"><i class="icon-doc"></i><span>.hgtags</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.travis.yml"><i class="icon-doc"></i><span>.travis.yml</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/MANIFEST.in"><i class="icon-doc"></i><span>MANIFEST.in</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/README.rst"><i class="icon-doc"></i><span>README.rst</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/run_test_and_report.sh"><i class="icon-doc"></i><span>run_test_and_report.sh</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/setup.cfg"><i class="icon-doc"></i><span>setup.cfg</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/setup.py"><i class="icon-doc"></i><span>setup.py</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/test_and_report.sh"><i class="icon-doc"></i><span>test_and_report.sh</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/tox.ini"><i class="icon-doc"></i><span>tox.ini</span></a>' % HG_REPO)
 
     def test_index_revision(self):
         self.log_user()
@@ -59,11 +59,10 @@
 
         #Test response...
 
-        response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs"><i class="icon-folder-open"></i><span>docs</span></a>')
-        response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests"><i class="icon-folder-open"></i><span>tests</span></a>')
-        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst"><i class="icon-doc"></i><span>README.rst</span></a>')
+        response.mustcontain('<a class="browser-dir ypjax-link" href="/%s/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs"><i class="icon-folder-open"></i><span>docs</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-dir ypjax-link" href="/%s/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests"><i class="icon-folder-open"></i><span>tests</span></a>' % HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst"><i class="icon-doc"></i><span>README.rst</span></a>' % HG_REPO)
         response.mustcontain('1.1 KiB')
-        response.mustcontain('text/x-python')
 
     def test_index_different_branch(self):
         self.log_user()
--- a/kallithea/tests/functional/test_followers.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_followers.py	Mon Jul 20 15:07:54 2015 +0200
@@ -10,7 +10,7 @@
                                     action='followers',
                                     repo_name=repo_name))
 
-        response.mustcontain("""test_admin""")
+        response.mustcontain(TEST_USER_ADMIN_LOGIN)
         response.mustcontain("""Started following""")
 
     def test_index_git(self):
@@ -20,5 +20,5 @@
                                     action='followers',
                                     repo_name=repo_name))
 
-        response.mustcontain("""test_admin""")
+        response.mustcontain(TEST_USER_ADMIN_LOGIN)
         response.mustcontain("""Started following""")
--- a/kallithea/tests/functional/test_forks.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_forks.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+
+import unittest
+
 from kallithea.tests import *
 from kallithea.tests.fixture import Fixture
 
@@ -12,15 +15,7 @@
 from kallithea.tests import *
 
 
-class _BaseTest(TestController):
-    """
-    Write all tests here
-    """
-    REPO = None
-    REPO_TYPE = None
-    NEW_REPO = None
-    REPO_FORK = None
-
+class _BaseFixture(unittest.TestCase):
     @classmethod
     def setup_class(cls):
         pass
@@ -40,6 +35,16 @@
         Session().delete(self.u1)
         Session().commit()
 
+
+class _BaseTestCase(object):
+    """
+    Write all tests here
+    """
+    REPO = None
+    REPO_TYPE = None
+    NEW_REPO = None
+    REPO_FORK = None
+
     def test_index(self):
         self.log_user()
         repo_name = self.REPO
@@ -218,14 +223,14 @@
         response.mustcontain('There are no forks yet')
 
 
-class TestGIT(_BaseTest):
+class TestGIT(TestController, _BaseTestCase, _BaseFixture):
     REPO = GIT_REPO
     NEW_REPO = NEW_GIT_REPO
     REPO_TYPE = 'git'
     REPO_FORK = GIT_FORK
 
 
-class TestHG(_BaseTest):
+class TestHG(TestController, _BaseTestCase, _BaseFixture):
     REPO = HG_REPO
     NEW_REPO = NEW_HG_REPO
     REPO_TYPE = 'hg'
--- a/kallithea/tests/functional/test_journal.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_journal.py	Mon Jul 20 15:07:54 2015 +0200
@@ -12,7 +12,7 @@
 
     def test_stop_following_repository(self):
         session = self.log_user()
-#        usr = Session().query(User).filter(User.username == 'test_admin').one()
+#        usr = Session().query(User).filter(User.username == TEST_USER_ADMIN_LOGIN).one()
 #        repo = Session().query(Repository).filter(Repository.repo_name == HG_REPO).one()
 #
 #        followings = Session().query(UserFollowing)\
--- a/kallithea/tests/functional/test_login.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_login.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,5 +1,7 @@
 # -*- coding: utf-8 -*-
 from __future__ import with_statement
+import re
+
 import mock
 from kallithea.tests import *
 from kallithea.tests.fixture import Fixture
@@ -15,12 +17,8 @@
 
 
 class TestLoginController(TestController):
-
-    def tearDown(self):
-        for n in Notification.query().all():
-            Session().delete(n)
-
-        Session().commit()
+    def setUp(self):
+        self.remove_all_notifications()
         self.assertEqual(Notification.query().all(), [])
 
     def test_index(self):
@@ -30,22 +28,22 @@
 
     def test_login_admin_ok(self):
         response = self.app.post(url(controller='login', action='index'),
-                                 {'username': 'test_admin',
-                                  'password': 'test12'})
+                                 {'username': TEST_USER_ADMIN_LOGIN,
+                                  'password': TEST_USER_ADMIN_PASS})
         self.assertEqual(response.status, '302 Found')
         self.assertEqual(response.session['authuser'].get('username'),
-                         'test_admin')
+                         TEST_USER_ADMIN_LOGIN)
         response = response.follow()
         response.mustcontain('/%s' % HG_REPO)
 
     def test_login_regular_ok(self):
         response = self.app.post(url(controller='login', action='index'),
-                                 {'username': 'test_regular',
-                                  'password': 'test12'})
+                                 {'username': TEST_USER_REGULAR_LOGIN,
+                                  'password': TEST_USER_REGULAR_PASS})
 
         self.assertEqual(response.status, '302 Found')
         self.assertEqual(response.session['authuser'].get('username'),
-                         'test_regular')
+                         TEST_USER_REGULAR_LOGIN)
         response = response.follow()
         response.mustcontain('/%s' % HG_REPO)
 
@@ -53,26 +51,64 @@
         test_came_from = '/_admin/users'
         response = self.app.post(url(controller='login', action='index',
                                      came_from=test_came_from),
-                                 {'username': 'test_admin',
-                                  'password': 'test12'})
+                                 {'username': TEST_USER_ADMIN_LOGIN,
+                                  'password': TEST_USER_ADMIN_PASS})
         self.assertEqual(response.status, '302 Found')
         response = response.follow()
 
         self.assertEqual(response.status, '200 OK')
         response.mustcontain('Users Administration')
 
+    def test_login_do_not_remember(self):
+        response = self.app.post(url(controller='login', action='index'),
+                                 {'username': TEST_USER_REGULAR_LOGIN,
+                                  'password': TEST_USER_REGULAR_PASS,
+                                  'remember': False})
+
+        self.assertIn('Set-Cookie', response.headers)
+        for cookie in response.headers.getall('Set-Cookie'):
+            self.assertFalse(re.search(r';\s+(Max-Age|Expires)=', cookie, re.IGNORECASE),
+                'Cookie %r has expiration date, but should be a session cookie' % cookie)
+
+    def test_login_remember(self):
+        response = self.app.post(url(controller='login', action='index'),
+                                 {'username': TEST_USER_REGULAR_LOGIN,
+                                  'password': TEST_USER_REGULAR_PASS,
+                                  'remember': True})
+
+        self.assertIn('Set-Cookie', response.headers)
+        for cookie in response.headers.getall('Set-Cookie'):
+            self.assertTrue(re.search(r';\s+(Max-Age|Expires)=', cookie, re.IGNORECASE),
+                'Cookie %r should have expiration date, but is a session cookie' % cookie)
+
+    def test_logout(self):
+        response = self.app.post(url(controller='login', action='index'),
+                                 {'username': TEST_USER_REGULAR_LOGIN,
+                                  'password': TEST_USER_REGULAR_PASS})
+
+        # Verify that a login session has been established.
+        response = self.app.get(url(controller='login', action='index'))
+        response = response.follow()
+        self.assertIn('authuser', response.session)
+
+        response.click('Log Out')
+
+        # Verify that the login session has been terminated.
+        response = self.app.get(url(controller='login', action='index'))
+        self.assertNotIn('authuser', response.session)
+
     @parameterized.expand([
           ('data:text/html,<script>window.alert("xss")</script>',),
           ('mailto:test@example.com',),
           ('file:///etc/passwd',),
           ('ftp://some.ftp.server',),
-          ('http://other.domain',),
+          ('http://other.domain/bl%C3%A5b%C3%A6rgr%C3%B8d',),
     ])
     def test_login_bad_came_froms(self, url_came_from):
         response = self.app.post(url(controller='login', action='index',
                                      came_from=url_came_from),
-                                 {'username': 'test_admin',
-                                  'password': 'test12'})
+                                 {'username': TEST_USER_ADMIN_LOGIN,
+                                  'password': TEST_USER_ADMIN_PASS})
         self.assertEqual(response.status, '302 Found')
         self.assertEqual(response._environ['paste.testing_variables']
                          ['tmpl_context'].came_from, '/')
@@ -82,7 +118,7 @@
 
     def test_login_short_password(self):
         response = self.app.post(url(controller='login', action='index'),
-                                 {'username': 'test_admin',
+                                 {'username': TEST_USER_ADMIN_LOGIN,
                                   'password': 'as'})
         self.assertEqual(response.status, '200 OK')
 
@@ -96,6 +132,66 @@
         response.mustcontain('invalid user name')
         response.mustcontain('invalid password')
 
+    # verify that get arguments are correctly passed along login redirection
+
+    @parameterized.expand([
+        ({'foo':'one', 'bar':'two'}, ('foo=one', 'bar=two')),
+        ({'blue': u'blå'.encode('utf-8'), 'green':u'grøn'},
+             ('blue=bl%C3%A5', 'green=gr%C3%B8n')),
+    ])
+    def test_redirection_to_login_form_preserves_get_args(self, args, args_encoded):
+        with fixture.anon_access(False):
+            response = self.app.get(url(controller='summary', action='index',
+                                        repo_name=HG_REPO,
+                                        **args))
+            self.assertEqual(response.status, '302 Found')
+            for encoded in args_encoded:
+                self.assertIn(encoded, response.location)
+
+    @parameterized.expand([
+        ({'foo':'one', 'bar':'two'}, ('foo=one', 'bar=two')),
+        ({'blue': u'blå'.encode('utf-8'), 'green':u'grøn'},
+             ('blue=bl%C3%A5', 'green=gr%C3%B8n')),
+    ])
+    def test_login_form_preserves_get_args(self, args, args_encoded):
+        response = self.app.get(url(controller='login', action='index',
+                                    came_from = '/_admin/users',
+                                    **args))
+        for encoded in args_encoded:
+            self.assertIn(encoded, response.form.action)
+
+    @parameterized.expand([
+        ({'foo':'one', 'bar':'two'}, ('foo=one', 'bar=two')),
+        ({'blue': u'blå'.encode('utf-8'), 'green':u'grøn'},
+             ('blue=bl%C3%A5', 'green=gr%C3%B8n')),
+    ])
+    def test_redirection_after_successful_login_preserves_get_args(self, args, args_encoded):
+        response = self.app.post(url(controller='login', action='index',
+                                     came_from = '/_admin/users',
+                                     **args),
+                                 {'username': TEST_USER_ADMIN_LOGIN,
+                                  'password': TEST_USER_ADMIN_PASS})
+        self.assertEqual(response.status, '302 Found')
+        for encoded in args_encoded:
+            self.assertIn(encoded, response.location)
+
+    @parameterized.expand([
+        ({'foo':'one', 'bar':'two'}, ('foo=one', 'bar=two')),
+        ({'blue': u'blå'.encode('utf-8'), 'green':u'grøn'},
+             ('blue=bl%C3%A5', 'green=gr%C3%B8n')),
+    ])
+    def test_login_form_after_incorrect_login_preserves_get_args(self, args, args_encoded):
+        response = self.app.post(url(controller='login', action='index',
+                                     came_from = '/_admin/users',
+                                     **args),
+                                 {'username': 'error',
+                                  'password': 'test12'})
+
+        response.mustcontain('invalid user name')
+        response.mustcontain('invalid password')
+        for encoded in args_encoded:
+            self.assertIn(encoded, response.form.action)
+
     #==========================================================================
     # REGISTRATIONS
     #==========================================================================
@@ -104,7 +200,7 @@
         response.mustcontain('Sign Up')
 
     def test_register_err_same_username(self):
-        uname = 'test_admin'
+        uname = TEST_USER_ADMIN_LOGIN
         response = self.app.post(url(controller='login', action='register'),
                                             {'username': uname,
                                              'password': 'test12',
@@ -122,7 +218,7 @@
                                             {'username': 'test_admin_0',
                                              'password': 'test12',
                                              'password_confirmation': 'test12',
-                                             'email': 'test_admin@mail.com',
+                                             'email': TEST_USER_ADMIN_EMAIL,
                                              'firstname': 'test',
                                              'lastname': 'test'})
 
@@ -134,7 +230,7 @@
                                             {'username': 'test_admin_1',
                                              'password': 'test12',
                                              'password_confirmation': 'test12',
-                                             'email': 'TesT_Admin@mail.COM',
+                                             'email': TEST_USER_ADMIN_EMAIL.title(),
                                              'firstname': 'test',
                                              'lastname': 'test'})
         msg = validators.UniqSystemEmail()()._messages['email_taken']
@@ -168,7 +264,7 @@
                 'alphanumeric character')
 
     def test_register_err_case_sensitive(self):
-        usr = 'Test_Admin'
+        usr = TEST_USER_ADMIN_LOGIN.title()
         response = self.app.post(url(controller='login', action='register'),
                                             {'username': usr,
                                              'password': 'test12',
@@ -208,7 +304,7 @@
     def test_register_ok(self):
         username = 'test_regular4'
         password = 'qweqwe'
-        email = 'marcin@test.com'
+        email = 'username@test.com'
         name = 'testname'
         lastname = 'testlastname'
 
@@ -233,15 +329,13 @@
         self.assertEqual(ret.admin, False)
 
     def test_forgot_password_wrong_mail(self):
-        bad_email = 'marcin@wrongmail.org'
+        bad_email = 'username%wrongmail.org'
         response = self.app.post(
                         url(controller='login', action='password_reset'),
                             {'email': bad_email, }
         )
 
-        msg = validators.ValidSystemEmail()._messages['non_existing_email']
-        msg = h.html_escape(msg % {'email': bad_email})
-        response.mustcontain()
+        response.mustcontain('An email address must contain a single @')
 
     def test_forgot_password(self):
         response = self.app.get(url(controller='login',
@@ -250,7 +344,7 @@
 
         username = 'test_password_reset_1'
         password = 'qweqwe'
-        email = 'marcin@python-works.com'
+        email = 'username@python-works.com'
         name = 'passwd'
         lastname = 'reset'
 
@@ -319,7 +413,7 @@
                 self.app.get(url(controller='changeset',
                                  action='changeset_raw',
                                  repo_name=HG_REPO, revision='tip', api_key=api_key),
-                             status=302)
+                             status=403)
 
     @parameterized.expand([
         ('none', None, 302),
@@ -365,7 +459,7 @@
 
             new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, u'test')
             Session().commit()
-            #patch the api key and make it expired
+            #patch the API key and make it expired
             new_api_key.expires = 0
             Session().add(new_api_key)
             Session().commit()
--- a/kallithea/tests/functional/test_my_account.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_my_account.py	Mon Jul 20 15:07:54 2015 +0200
@@ -23,7 +23,7 @@
         self.log_user()
         response = self.app.get(url('my_account'))
 
-        response.mustcontain('value="test_admin')
+        response.mustcontain('value="%s' % TEST_USER_ADMIN_LOGIN)
 
     def test_my_account_my_repos(self):
         self.log_user()
@@ -149,11 +149,11 @@
     def test_my_account_update_err_email_exists(self):
         self.log_user()
 
-        new_email = 'test_regular@mail.com'  # already exisitn email
+        new_email = TEST_USER_REGULAR_EMAIL  # already existing email
         response = self.app.post(url('my_account'),
                                 params=dict(
-                                    username='test_admin',
-                                    new_password='test12',
+                                    username=TEST_USER_ADMIN_LOGIN,
+                                    new_password=TEST_USER_ADMIN_PASS,
                                     password_confirmation='test122',
                                     firstname='NewName',
                                     lastname='NewLastname',
@@ -164,13 +164,13 @@
         response.mustcontain('This e-mail address is already taken')
 
     def test_my_account_update_err(self):
-        self.log_user('test_regular2', 'test12')
+        self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
 
         new_email = 'newmail.pl'
         response = self.app.post(url('my_account'),
                                  params=dict(
-                                            username='test_admin',
-                                            new_password='test12',
+                                            username=TEST_USER_ADMIN_LOGIN,
+                                            new_password=TEST_USER_ADMIN_PASS,
                                             password_confirmation='test122',
                                             firstname='NewName',
                                             lastname='NewLastname',
@@ -181,15 +181,15 @@
         from kallithea.model import validators
         msg = validators.ValidUsername(edit=False, old_data={})\
                 ._messages['username_exists']
-        msg = h.html_escape(msg % {'username': 'test_admin'})
+        msg = h.html_escape(msg % {'username': TEST_USER_ADMIN_LOGIN})
         response.mustcontain(u"%s" % msg)
 
     def test_my_account_api_keys(self):
-        usr = self.log_user('test_regular2', 'test12')
+        usr = self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
         user = User.get(usr['user_id'])
         response = self.app.get(url('my_account_api_keys'))
         response.mustcontain(user.api_key)
-        response.mustcontain('expires: never')
+        response.mustcontain('Expires: Never')
 
     @parameterized.expand([
         ('forever', -1),
@@ -197,11 +197,11 @@
         ('30days', 60*60*24*30),
     ])
     def test_my_account_add_api_keys(self, desc, lifetime):
-        usr = self.log_user('test_regular2', 'test12')
+        usr = self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
         user = User.get(usr['user_id'])
         response = self.app.post(url('my_account_api_keys'),
                                  {'description': desc, 'lifetime': lifetime, '_authentication_token': self.authentication_token()})
-        self.checkSessionFlash(response, 'Api key successfully created')
+        self.checkSessionFlash(response, 'API key successfully created')
         try:
             response = response.follow()
             user = User.get(usr['user_id'])
@@ -213,11 +213,11 @@
                 Session().commit()
 
     def test_my_account_remove_api_key(self):
-        usr = self.log_user('test_regular2', 'test12')
+        usr = self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
         user = User.get(usr['user_id'])
         response = self.app.post(url('my_account_api_keys'),
                                  {'description': 'desc', 'lifetime': -1, '_authentication_token': self.authentication_token()})
-        self.checkSessionFlash(response, 'Api key successfully created')
+        self.checkSessionFlash(response, 'API key successfully created')
         response = response.follow()
 
         #now delete our key
@@ -226,21 +226,21 @@
 
         response = self.app.post(url('my_account_api_keys'),
                  {'_method': 'delete', 'del_api_key': keys[0].api_key, '_authentication_token': self.authentication_token()})
-        self.checkSessionFlash(response, 'Api key successfully deleted')
+        self.checkSessionFlash(response, 'API key successfully deleted')
         keys = UserApiKeys.query().all()
         self.assertEqual(0, len(keys))
 
 
     def test_my_account_reset_main_api_key(self):
-        usr = self.log_user('test_regular2', 'test12')
+        usr = self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
         user = User.get(usr['user_id'])
         api_key = user.api_key
         response = self.app.get(url('my_account_api_keys'))
         response.mustcontain(api_key)
-        response.mustcontain('expires: never')
+        response.mustcontain('Expires: Never')
 
         response = self.app.post(url('my_account_api_keys'),
                  {'_method': 'delete', 'del_api_key_builtin': api_key, '_authentication_token': self.authentication_token()})
-        self.checkSessionFlash(response, 'Api key successfully reset')
+        self.checkSessionFlash(response, 'API key successfully reset')
         response = response.follow()
         response.mustcontain(no=[api_key])
--- a/kallithea/tests/functional/test_pullrequests.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_pullrequests.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,8 +1,11 @@
+import re
+
 from kallithea.tests import *
 from kallithea.tests.fixture import Fixture
 from kallithea.model.meta import Session
 
 from kallithea.controllers.pullrequests import PullrequestsController
+from kallithea.lib.exceptions import UserInvalidException
 
 fixture = Fixture()
 
@@ -13,6 +16,132 @@
         response = self.app.get(url(controller='pullrequests', action='index',
                                     repo_name=HG_REPO))
 
+    def test_create_trivial(self):
+        self.log_user()
+        response = self.app.post(url(controller='pullrequests', action='create',
+                                     repo_name=HG_REPO),
+                                 {'org_repo': HG_REPO,
+                                  'org_ref': 'branch:default:default',
+                                  'other_repo': HG_REPO,
+                                  'other_ref': 'branch:default:default',
+                                  'pullrequest_title': 'title',
+                                  'pullrequest_desc': 'description',
+                                  '_authentication_token': self.authentication_token(),
+                                 }
+                                )
+        self.assertEqual(response.status, '302 Found')
+        response = response.follow()
+        self.assertEqual(response.status, '200 OK')
+        response.mustcontain('This pull request has already been merged to default.')
+
+    def test_create_with_existing_reviewer(self):
+        self.log_user()
+        response = self.app.post(url(controller='pullrequests', action='create',
+                                     repo_name=HG_REPO),
+                                 {'org_repo': HG_REPO,
+                                  'org_ref': 'branch:default:default',
+                                  'other_repo': HG_REPO,
+                                  'other_ref': 'branch:default:default',
+                                  'pullrequest_title': 'title',
+                                  'pullrequest_desc': 'description',
+                                  '_authentication_token': self.authentication_token(),
+                                  'review_members': TEST_USER_ADMIN_LOGIN,
+                                 }
+                                )
+        self.assertEqual(response.status, '302 Found')
+        response = response.follow()
+        self.assertEqual(response.status, '200 OK')
+        response.mustcontain('This pull request has already been merged to default.')
+
+    def test_create_with_invalid_reviewer(self):
+        invalid_user_name = 'invalid_user'
+        self.log_user()
+        response = self.app.post(url(controller='pullrequests', action='create',
+                                     repo_name=HG_REPO),
+                                 {
+                                  'org_repo': HG_REPO,
+                                  'org_ref': 'branch:default:default',
+                                  'other_repo': HG_REPO,
+                                  'other_ref': 'branch:default:default',
+                                  'pullrequest_title': 'title',
+                                  'pullrequest_desc': 'description',
+                                  '_authentication_token': self.authentication_token(),
+                                  'review_members': invalid_user_name,
+                                 },
+                                 status=400)
+        response.mustcontain('Invalid reviewer &#34;%s&#34; specified' % invalid_user_name)
+
+    def test_update_with_invalid_reviewer(self):
+        invalid_user_id = 99999
+        self.log_user()
+        # create a valid pull request
+        response = self.app.post(url(controller='pullrequests', action='create',
+                                     repo_name=HG_REPO),
+                                 {
+                                  'org_repo': HG_REPO,
+                                  'org_ref': 'branch:default:default',
+                                  'other_repo': HG_REPO,
+                                  'other_ref': 'branch:default:default',
+                                  'pullrequest_title': 'title',
+                                  'pullrequest_desc': 'description',
+                                  '_authentication_token': self.authentication_token(),
+                                 }
+                                )
+        self.assertEqual(response.status, '302 Found')
+        # location is of the form:
+        # http://localhost/vcs_test_hg/pull-request/54/_/title
+        m = re.search('/pull-request/(\d+)/', response.location)
+        self.assertNotEqual(m, None)
+        pull_request_id = m.group(1)
+
+        # update it
+        response = self.app.post(url(controller='pullrequests', action='post',
+                                     repo_name=HG_REPO, pull_request_id=pull_request_id),
+                                 {
+                                  'updaterev': 'default',
+                                  'pullrequest_title': 'title',
+                                  'pullrequest_desc': 'description',
+                                  '_authentication_token': self.authentication_token(),
+                                  'review_members': invalid_user_id,
+                                 },
+                                 status=400)
+        response.mustcontain('Invalid reviewer &#34;%s&#34; specified' % invalid_user_id)
+
+    def test_edit_with_invalid_reviewer(self):
+        invalid_user_id = 99999
+        self.log_user()
+        # create a valid pull request
+        response = self.app.post(url(controller='pullrequests', action='create',
+                                     repo_name=HG_REPO),
+                                 {
+                                  'org_repo': HG_REPO,
+                                  'org_ref': 'branch:default:default',
+                                  'other_repo': HG_REPO,
+                                  'other_ref': 'branch:default:default',
+                                  'pullrequest_title': 'title',
+                                  'pullrequest_desc': 'description',
+                                  '_authentication_token': self.authentication_token(),
+                                 }
+                                )
+        self.assertEqual(response.status, '302 Found')
+        # location is of the form:
+        # http://localhost/vcs_test_hg/pull-request/54/_/title
+        m = re.search('/pull-request/(\d+)/', response.location)
+        self.assertNotEqual(m, None)
+        pull_request_id = m.group(1)
+
+        # edit it
+        response = self.app.post(url(controller='pullrequests', action='post',
+                                     repo_name=HG_REPO, pull_request_id=pull_request_id),
+                                 {
+                                  'pullrequest_title': 'title',
+                                  'pullrequest_desc': 'description',
+                                  '_authentication_token': self.authentication_token(),
+                                  'review_members': invalid_user_id,
+                                 },
+                                 status=400)
+        response.mustcontain('Invalid reviewer &#34;%s&#34; specified' % invalid_user_id)
+
 class TestPullrequestsGetRepoRefs(TestController):
 
     def setUp(self):
--- a/kallithea/tests/functional/test_summary.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/functional/test_summary.py	Mon Jul 20 15:07:54 2015 +0200
@@ -41,8 +41,8 @@
         )
 
         # clone url...
-        response.mustcontain('''id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/%s"''' % HG_REPO)
-        response.mustcontain('''id="clone_url_id" readonly="readonly" value="http://test_admin@localhost:80/_%s"''' % ID)
+        response.mustcontain('''id="clone_url" readonly="readonly" value="http://%s@localhost:80/%s"''' % (TEST_USER_ADMIN_LOGIN, HG_REPO))
+        response.mustcontain('''id="clone_url_id" readonly="readonly" value="http://%s@localhost:80/_%s"''' % (TEST_USER_ADMIN_LOGIN, ID))
 
     def test_index_git(self):
         self.log_user()
@@ -61,8 +61,8 @@
         )
 
         # clone url...
-        response.mustcontain('''id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/%s"''' % GIT_REPO)
-        response.mustcontain('''id="clone_url_id" readonly="readonly" value="http://test_admin@localhost:80/_%s"''' % ID)
+        response.mustcontain('''id="clone_url" readonly="readonly" value="http://%s@localhost:80/%s"''' % (TEST_USER_ADMIN_LOGIN, GIT_REPO))
+        response.mustcontain('''id="clone_url_id" readonly="readonly" value="http://%s@localhost:80/_%s"''' % (TEST_USER_ADMIN_LOGIN, ID))
 
     def test_index_by_id_hg(self):
         self.log_user()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/models/test_changeset_status.py	Mon Jul 20 15:07:54 2015 +0200
@@ -0,0 +1,40 @@
+from kallithea.tests import *
+from kallithea.model.changeset_status import ChangesetStatusModel
+from kallithea.model.db import ChangesetStatus as CS
+
+class CSM(object): # ChangesetStatusMock
+
+    def __init__(self, status):
+        self.status = status
+
+class TestChangesetStatusCalculation(BaseTestCase):
+
+    def setUp(self):
+        self.m = ChangesetStatusModel()
+
+    @parameterized.expand([
+        ('empty list', CS.STATUS_UNDER_REVIEW, []),
+        ('approve', CS.STATUS_APPROVED, [CSM(CS.STATUS_APPROVED)]),
+        ('approve2', CS.STATUS_APPROVED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_APPROVED)]),
+        ('approve_reject', CS.STATUS_REJECTED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_REJECTED)]),
+        ('approve_underreview', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_UNDER_REVIEW)]),
+        ('approve_notreviewed', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_NOT_REVIEWED)]),
+        ('underreview', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_UNDER_REVIEW), CSM(CS.STATUS_UNDER_REVIEW)]),
+        ('reject', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED)]),
+        ('reject_underreview', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_UNDER_REVIEW)]),
+        ('reject_notreviewed', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_NOT_REVIEWED)]),
+        ('notreviewed', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_NOT_REVIEWED)]),
+        ('approve_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), None]),
+        ('approve2_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_APPROVED), None]),
+        ('approve_reject_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_REJECTED), None]),
+        ('approve_underreview_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_UNDER_REVIEW), None]),
+        ('approve_notreviewed_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_NOT_REVIEWED), None]),
+        ('underreview_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_UNDER_REVIEW), CSM(CS.STATUS_UNDER_REVIEW), None]),
+        ('reject_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), None]),
+        ('reject_underreview_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_UNDER_REVIEW), None]),
+        ('reject_notreviewed_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_NOT_REVIEWED), None]),
+        ('notreviewed_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_NOT_REVIEWED), None]),
+    ])
+    def test_result(self, name, expected_result, statuses):
+        result = self.m._calculate_status(statuses)
+        self.assertEqual(result, expected_result)
--- a/kallithea/tests/models/test_diff_parsers.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/models/test_diff_parsers.py	Mon Jul 20 15:07:54 2015 +0200
@@ -275,3 +275,32 @@
         data = [(x['filename'], x['operation'], x['stats']) for x in diff_proc_d]
         expected_data = DIFF_FIXTURES[diff_fixture]
         self.assertListEqual(expected_data, data)
+
+    def test_diff_markup(self):
+        diff = fixture.load_resource('markuptest.diff', strip=False)
+        diff_proc = DiffProcessor(diff)
+        diff_proc_d = diff_proc.prepare()
+        chunks = diff_proc_d[0]['chunks']
+        self.assertFalse(chunks[0])
+        #from pprint import pprint; pprint(chunks[1])
+        l = ['\n']
+        for d in chunks[1]:
+            l.append('%(action)-7s %(new_lineno)3s %(old_lineno)3s %(line)r\n' % d)
+        s = ''.join(l)
+        print s
+        self.assertEqual(s, r'''
+context ... ... u'@@ -51,5 +51,12 @@\n'
+unmod    51  51 u'<u>\t</u>begin();\n'
+unmod    52  52 u'<u>\t</u>\n'
+add      53     u'<u>\t</u>int foo;<u class="cr"></u>\n'
+add      54     u'<u>\t</u>int bar; <u class="cr"></u>\n'
+add      55     u'<u>\t</u>int baz;<u>\t</u><u class="cr"></u>\n'
+add      56     u'<u>\t</u>int space; <i></i>'
+add      57     u'<u>\t</u>int tab;<u>\t</u>\n'
+add      58     u'<u>\t</u>\n'
+unmod    59  53 u' <i></i>'
+del          54 u'<u>\t</u><del>#define MAX_STEPS (48)</del>\n'
+add      60     u'<u>\t</u><ins><u class="cr"></u></ins>\n'
+add      61     u'<u>\t</u>#define MAX_STEPS (64)<u class="cr"></u>\n'
+unmod    62  55 u'\n'
+''')
--- a/kallithea/tests/models/test_notifications.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/models/test_notifications.py	Mon Jul 20 15:07:54 2015 +0200
@@ -34,20 +34,12 @@
 
         super(TestNotifications, self).__init__(methodName=methodName)
 
-    def _clean_notifications(self):
-        for n in Notification.query().all():
-            Session().delete(n)
-
-        Session().commit()
-        self.assertEqual(Notification.query().all(), [])
-
-    def tearDown(self):
-        self._clean_notifications()
-
-    def test_create_notification(self):
+    def setUp(self):
+        self.remove_all_notifications()
         self.assertEqual([], Notification.query().all())
         self.assertEqual([], UserNotification.query().all())
 
+    def test_create_notification(self):
         usrs = [self.u1, self.u2]
         notification = NotificationModel().create(created_by=self.u1,
                                            subject=u'subj', body=u'hi there',
@@ -71,9 +63,6 @@
                          set(usrs))
 
     def test_user_notifications(self):
-        self.assertEqual([], Notification.query().all())
-        self.assertEqual([], UserNotification.query().all())
-
         notification1 = NotificationModel().create(created_by=self.u1,
                                             subject=u'subj', body=u'hi there1',
                                             recipients=[self.u3])
@@ -88,9 +77,6 @@
                          sorted([notification2, notification1]))
 
     def test_delete_notifications(self):
-        self.assertEqual([], Notification.query().all())
-        self.assertEqual([], UserNotification.query().all())
-
         notification = NotificationModel().create(created_by=self.u1,
                                            subject=u'title', body=u'hi there3',
                                     recipients=[self.u3, self.u1, self.u2])
@@ -109,10 +95,6 @@
         self.assertEqual(un, [])
 
     def test_delete_association(self):
-
-        self.assertEqual([], Notification.query().all())
-        self.assertEqual([], UserNotification.query().all())
-
         notification = NotificationModel().create(created_by=self.u1,
                                            subject=u'title', body=u'hi there3',
                                     recipients=[self.u3, self.u1, self.u2])
@@ -156,10 +138,6 @@
         self.assertNotEqual(u2notification, None)
 
     def test_notification_counter(self):
-        self._clean_notifications()
-        self.assertEqual([], Notification.query().all())
-        self.assertEqual([], UserNotification.query().all())
-
         NotificationModel().create(created_by=self.u1,
                             subject=u'title', body=u'hi there_delete',
                             recipients=[self.u3, self.u1])
--- a/kallithea/tests/models/test_permissions.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/models/test_permissions.py	Mon Jul 20 15:07:54 2015 +0200
@@ -72,7 +72,7 @@
             'repositories_groups': {},
             'global': set([u'hg.create.repository', u'repository.read',
                            u'hg.register.manual_activate']),
-            'repositories': {u'vcs_test_hg': u'repository.read'}
+            'repositories': {HG_REPO: u'repository.read'}
         }
         self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
                          perms['repositories'][HG_REPO])
@@ -90,7 +90,7 @@
         perms = {
             'repositories_groups': {},
             'global': set([u'hg.admin', 'hg.create.write_on_repogroup.true']),
-            'repositories': {u'vcs_test_hg': u'repository.admin'}
+            'repositories': {HG_REPO: u'repository.admin'}
         }
         self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
                          perms['repositories'][HG_REPO])
@@ -111,7 +111,7 @@
         perms = {
             'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
             'global': set(Permission.DEFAULT_USER_PERMISSIONS),
-            'repositories': {u'vcs_test_hg': u'repository.read'}
+            'repositories': {HG_REPO: u'repository.read'}
         }
         self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
                          perms['repositories'][HG_REPO])
@@ -127,7 +127,7 @@
         perms = {
             'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
             'global': set(['hg.admin', 'hg.create.write_on_repogroup.true']),
-            'repositories': {u'vcs_test_hg': 'repository.admin'}
+            'repositories': {HG_REPO: 'repository.admin'}
         }
 
         self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
@@ -160,7 +160,7 @@
             'repositories_groups': {},
             'global': set([u'hg.create.repository', u'repository.read',
                            u'hg.register.manual_activate']),
-            'repositories': {u'vcs_test_hg': u'repository.read'}
+            'repositories': {HG_REPO: u'repository.read'}
         }
         self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
                          new_perm)
@@ -183,7 +183,7 @@
             'repositories_groups': {},
             'global': set([u'hg.create.repository', u'repository.read',
                            u'hg.register.manual_activate']),
-            'repositories': {u'vcs_test_hg': u'repository.read'}
+            'repositories': {HG_REPO: u'repository.read'}
         }
         self.assertEqual(u3_auth.permissions['repositories'][HG_REPO],
                          new_perm_gr)
@@ -217,7 +217,7 @@
             'repositories_groups': {},
             'global': set([u'hg.create.repository', u'repository.read',
                            u'hg.register.manual_activate']),
-            'repositories': {u'vcs_test_hg': u'repository.write'}
+            'repositories': {HG_REPO: u'repository.write'}
         }
         self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
                          new_perm_h)
--- a/kallithea/tests/models/test_user_group_permissions_on_repo_groups.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/models/test_user_group_permissions_on_repo_groups.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,11 +1,9 @@
 import functools
-from kallithea.tests import *
 
 from kallithea.model.repo_group import RepoGroupModel
 from kallithea.model.db import RepoGroup
 
 from kallithea.model.meta import Session
-from nose.tools import with_setup
 from kallithea.tests.models.common import _create_project_tree, check_tree_perms, \
     _get_perms, _check_expected_count, expected_count, _destroy_project_tree
 from kallithea.model.user_group import UserGroupModel
@@ -26,6 +24,13 @@
     repo_group = RepoGroup.get_by_group_name(group_name=group_name)
     if not repo_group:
         raise Exception('Cannot get group %s' % group_name)
+
+    # Start with a baseline that current group can read recursive
+    perms_updates = [[test_u2_gr_id, 'group.read', 'users_group']]
+    RepoGroupModel()._update_permissions(repo_group,
+                                         perms_updates=perms_updates,
+                                         recursive='all', check_perms=False)
+
     perms_updates = [[test_u2_gr_id, perm, 'users_group']]
     RepoGroupModel()._update_permissions(repo_group,
                                          perms_updates=perms_updates,
@@ -53,9 +58,9 @@
 
 def teardown_module():
     _destroy_project_tree(test_u2_id)
+    fixture.destroy_user_group('perms_group_1')
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_without_recursive_mode():
     # set permission to g0 non-recursive mode
     recursive = 'none'
@@ -75,7 +80,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_without_recursive_mode_subgroup():
     # set permission to g0 non-recursive mode
     recursive = 'none'
@@ -95,7 +99,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode():
 
     # set permission to g0 recursive mode, all children including
@@ -115,7 +118,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_inner_group():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -133,7 +135,6 @@
         yield check_tree_perms, name, perm, group, 'group.none'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_deepest():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -151,7 +152,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -168,7 +168,7 @@
     for name, perm in items:
         yield check_tree_perms, name, perm, group, 'group.admin'
 
-@with_setup(permissions_setup_func)
+
 def test_user_permissions_on_group_with_recursive_mode_on_repos():
     # set permission to g0/g0_1 with recursive mode on just repositories
     recursive = 'repos'
@@ -192,7 +192,7 @@
             old_perm = perm
         yield check_tree_perms, name, perm, group, old_perm
 
-@with_setup(permissions_setup_func)
+
 def test_user_permissions_on_group_with_recursive_mode_on_repo_groups():
     # set permission to g0/g0_1 with recursive mode on just repository groups
     recursive = 'groups'
--- a/kallithea/tests/models/test_user_groups.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/models/test_user_groups.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,6 +1,6 @@
 from kallithea.model.db import User
 
-from kallithea.tests import *
+from kallithea.tests import BaseTestCase, parameterized, TEST_USER_REGULAR_LOGIN
 from kallithea.tests.fixture import Fixture
 
 from kallithea.model.user_group import UserGroupModel
@@ -18,7 +18,6 @@
             fixture.destroy_user_group(gr)
         Session().commit()
 
-
     @parameterized.expand([
         ([], [], [], [], []),
         ([], ['regular'], [], [], ['regular']),  # no changes of regular
--- a/kallithea/tests/models/test_user_permissions_on_repo_groups.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/models/test_user_permissions_on_repo_groups.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,11 +1,9 @@
 import functools
-from kallithea.tests import *
 
 from kallithea.model.repo_group import RepoGroupModel
 from kallithea.model.db import RepoGroup, User
 
 from kallithea.model.meta import Session
-from nose.tools import with_setup
 from kallithea.tests.models.common import _create_project_tree, check_tree_perms, \
     _get_perms, _check_expected_count, expected_count, _destroy_project_tree
 
@@ -22,7 +20,6 @@
     """
     if not user_id:
         user_id = test_u1_id
-        # called by the @with_setup decorator also reset the default user stuff
         permissions_setup_func(group_name, perm, recursive,
                                user_id=User.get_default_user().user_id)
 
@@ -30,12 +27,19 @@
     if not repo_group:
         raise Exception('Cannot get group %s' % group_name)
 
+    # Start with a baseline that current group can read recursive
+    perms_updates = [[user_id, 'group.read', 'user']]
+    RepoGroupModel()._update_permissions(repo_group,
+                                         perms_updates=perms_updates,
+                                         recursive='all', check_perms=False)
+
     perms_updates = [[user_id, perm, 'user']]
     RepoGroupModel()._update_permissions(repo_group,
                                          perms_updates=perms_updates,
                                          recursive=recursive, check_perms=False)
     Session().commit()
 
+
 def setup_module():
     global test_u1_id, _get_repo_perms, _get_group_perms
     test_u1 = _create_project_tree()
@@ -51,7 +55,6 @@
     _destroy_project_tree(test_u1_id)
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_without_recursive_mode():
     # set permission to g0 non-recursive mode
     recursive = 'none'
@@ -71,7 +74,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_without_recursive_mode_subgroup():
     # set permission to g0 non-recursive mode
     recursive = 'none'
@@ -91,7 +93,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode():
 
     # set permission to g0 recursive mode, all children including
@@ -111,7 +112,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_for_default_user():
 
     # set permission to g0 recursive mode, all children including
@@ -139,7 +139,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_inner_group():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -157,7 +156,6 @@
         yield check_tree_perms, name, perm, group, 'group.none'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_deepest():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -175,7 +173,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -193,7 +190,6 @@
         yield check_tree_perms, name, perm, group, 'group.admin'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_repo_mode_for_default_user():
     # set permission to g0/g0_1 recursive repos only mode, all children including
     # other repos should have this permission now set, inner groups are excluded!
@@ -228,7 +224,6 @@
         yield check_tree_perms, name, perm, group, old_perm
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_repo_mode_inner_group():
     ## set permission to g0_3 group to none, with recursive repos only
     recursive = 'repos'
@@ -253,7 +248,6 @@
         yield check_tree_perms, name, perm, group, old_perm
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_group_mode_for_default_user():
     # set permission to g0/g0_1 with recursive groups only mode, all children including
     # other groups should have this permission now set. repositories should
@@ -281,7 +275,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_group_mode_inner_group():
     ## set permission to g0_3 group to none, with recursive mode for groups only
     recursive = 'groups'
--- a/kallithea/tests/nose_parametrized.py	Mon Jul 20 15:07:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,238 +0,0 @@
-import re
-import new
-import inspect
-import logging
-import logging.handlers
-from functools import wraps
-
-from nose.tools import nottest
-from unittest import TestCase
-
-
-def _terrible_magic_get_defining_classes():
-    """ Returns the set of parent classes of the class currently being defined.
-        Will likely only work if called from the ``parameterized`` decorator.
-        This function is entirely @brandon_rhodes's fault, as he suggested
-        the implementation: http://stackoverflow.com/a/8793684/71522
-        """
-    stack = inspect.stack()
-    if len(stack) <= 4:
-        return []
-    frame = stack[3]
-    code_context = frame[4][0].strip()
-    if not code_context.startswith("class "):
-        return []
-    _, parents = code_context.split("(", 1)
-    parents, _ = parents.rsplit(")", 1)
-    return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals)
-
-
-def parameterized(input):
-    """ Parameterize a test case:
-        >>> add1_tests = [(1, 2), (2, 3)]
-        >>> class TestFoo(object):
-        ...     @parameterized(add1_tests)
-        ...     def test_add1(self, input, expected):
-        ...         assert_equal(add1(input), expected)
-        >>> @parameterized(add1_tests)
-        ... def test_add1(input, expected):
-        ...     assert_equal(add1(input), expected)
-        >>>
-        """
-
-    if not hasattr(input, "__iter__"):
-        raise ValueError("expected iterable input; got %r" % (input,))
-
-    def parameterized_helper(f):
-        attached_instance_method = [False]
-
-        parent_classes = _terrible_magic_get_defining_classes()
-        if any(issubclass(cls, TestCase) for cls in parent_classes):
-            raise Exception("Warning: '@parameterized' tests won't work "
-                            "inside subclasses of 'TestCase' - use "
-                            "'@parameterized.expand' instead")
-
-        @wraps(f)
-        def parameterized_helper_method(self=None):
-            if self is not None and not attached_instance_method[0]:
-                # confusingly, we need to create a named instance method and
-                # attach that to the class...
-                cls = self.__class__
-                im_f = new.instancemethod(f, None, cls)
-                setattr(cls, f.__name__, im_f)
-                attached_instance_method[0] = True
-            for args in input:
-                if isinstance(args, basestring):
-                    args = [args]
-                # ... then pull that named instance method off, turning it into
-                # a bound method ...
-                if self is not None:
-                    args = [getattr(self, f.__name__)] + list(args)
-                else:
-                    args = [f] + list(args)
-                # ... then yield that as a tuple. If those steps aren't
-                # followed precicely, Nose gets upset and doesn't run the test
-                # or doesn't run setup methods.
-                yield tuple(args)
-
-        f.__name__ = "_helper_for_%s" % (f.__name__,)
-        parameterized_helper_method.parameterized_input = input
-        parameterized_helper_method.parameterized_func = f
-        return parameterized_helper_method
-
-    return parameterized_helper
-
-
-def to_safe_name(s):
-    return re.sub("[^a-zA-Z0-9_]", "", s)
-
-
-def parameterized_expand_helper(func_name, func, args):
-    def parameterized_expand_helper_helper(self=()):
-        if self != ():
-            self = (self,)
-        return func(*(self + args))
-    parameterized_expand_helper_helper.__name__ = str(func_name)
-    return parameterized_expand_helper_helper
-
-
-def parameterized_expand(input):
-    """ A "brute force" method of parameterizing test cases. Creates new test
-        cases and injects them into the namespace that the wrapped function
-        is being defined in. Useful for parameterizing tests in subclasses
-        of 'UnitTest', where Nose test generators don't work.
-
-        >>> @parameterized.expand([("foo", 1, 2)])
-        ... def test_add1(name, input, expected):
-        ...     actual = add1(input)
-        ...     assert_equal(actual, expected)
-        ...
-        >>> locals()
-        ... 'test_add1_foo_0': <function ...> ...
-        >>>
-        """
-
-    def parameterized_expand_wrapper(f):
-        stack = inspect.stack()
-        frame = stack[1]
-        frame_locals = frame[0].f_locals
-
-        base_name = f.__name__
-        for num, args in enumerate(input):
-            name_suffix = "_%s" % (num,)
-            if len(args) > 0 and isinstance(args[0], basestring):
-                name_suffix += "_" + to_safe_name(args[0])
-            name = base_name + name_suffix
-            new_func = parameterized_expand_helper(name, f, args)
-            frame_locals[name] = new_func
-        return nottest(f)
-    return parameterized_expand_wrapper
-
-parameterized.expand = parameterized_expand
-
-
-def assert_contains(haystack, needle):
-    if needle not in haystack:
-        raise AssertionError("%r not in %r" % (needle, haystack))
-
-
-def assert_not_contains(haystack, needle):
-    if needle in haystack:
-        raise AssertionError("%r in %r" % (needle, haystack))
-
-
-def imported_from_test():
-    """ Returns true if it looks like this module is being imported by unittest
-        or nose. """
-    import re
-    import inspect
-    nose_re = re.compile(r"\bnose\b")
-    unittest_re = re.compile(r"\bunittest2?\b")
-    for frame in inspect.stack():
-        file = frame[1]
-        if nose_re.search(file) or unittest_re.search(file):
-            return True
-    return False
-
-
-def assert_raises(func, exc_type, str_contains=None, repr_contains=None):
-    try:
-        func()
-    except exc_type, e:
-        if str_contains is not None and str_contains not in str(e):
-            raise AssertionError("%s raised, but %r does not contain %r"
-                                 % (exc_type, str(e), str_contains))
-        if repr_contains is not None and repr_contains not in repr(e):
-            raise AssertionError("%s raised, but %r does not contain %r"
-                                 % (exc_type, repr(e), repr_contains))
-        return e
-    else:
-        raise AssertionError("%s not raised" % (exc_type,))
-
-
-log_handler = None
-
-
-def setup_logging():
-    """ Configures a log handler which will capure log messages during a test.
-        The ``logged_messages`` and ``assert_no_errors_logged`` functions can be
-        used to make assertions about these logged messages.
-
-        For example::
-
-            from ensi_common.testing import (
-                setup_logging, teardown_logging, assert_no_errors_logged,
-                assert_logged,
-            )
-
-            class TestWidget(object):
-                def setup(self):
-                    setup_logging()
-
-                def teardown(self):
-                    assert_no_errors_logged()
-                    teardown_logging()
-
-                def test_that_will_fail(self):
-                    log.warning("this warning message will trigger a failure")
-
-                def test_that_will_pass(self):
-                    log.info("but info messages are ok")
-                    assert_logged("info messages are ok")
-        """
-
-    global log_handler
-    if log_handler is not None:
-        logging.getLogger().removeHandler(log_handler)
-    log_handler = logging.handlers.BufferingHandler(1000)
-    formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
-    log_handler.setFormatter(formatter)
-    logging.getLogger().addHandler(log_handler)
-
-
-def teardown_logging():
-    global log_handler
-    if log_handler is not None:
-        logging.getLogger().removeHandler(log_handler)
-        log_handler = None
-
-
-def logged_messages():
-    assert log_handler, "setup_logging not called"
-    return [(log_handler.format(record), record) for record in log_handler.buffer]
-
-
-def assert_no_errors_logged():
-    for _, record in logged_messages():
-        if record.levelno >= logging.WARNING:
-            # Assume that the nose log capture plugin is being used, so it will
-            # show the exception.
-            raise AssertionError("an unexpected error was logged")
-
-
-def assert_logged(expected_msg_contents):
-    for msg, _ in logged_messages():
-        if expected_msg_contents in msg:
-            return
-    raise AssertionError("no logged message contains %r"
-                         % (expected_msg_contents,))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/other/manual_test_vcs_operations.py	Mon Jul 20 15:07:54 2015 +0200
@@ -0,0 +1,536 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.tests.test_scm_operations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Test suite for making push/pull operations.
+
+Run it in two terminals::
+ paster serve test.ini
+ KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 nosetests kallithea/tests/other/manual_test_vcs_operations.py
+
+You must have git > 1.8.1 for tests to work fine
+
+This file was forked by the Kallithea project in July 2014.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: Dec 30, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+
+"""
+
+import tempfile
+import time
+from os.path import join as jn
+
+from tempfile import _RandomNameSequence
+from subprocess import Popen, PIPE
+
+from kallithea.tests import *
+from kallithea.model.db import User, Repository, UserIpMap, CacheInvalidation
+from kallithea.model.meta import Session
+from kallithea.model.repo import RepoModel
+from kallithea.model.user import UserModel
+
+DEBUG = True
+HOST = '127.0.0.1:5000'  # test host
+
+
+class Command(object):
+
+    def __init__(self, cwd):
+        self.cwd = cwd
+
+    def execute(self, cmd, *args):
+        """
+        Runs command on the system with given ``args``.
+        """
+
+        command = cmd + ' ' + ' '.join(args)
+        if DEBUG:
+            print '*** CMD %s ***' % command
+        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
+        stdout, stderr = p.communicate()
+        if DEBUG:
+            print 'stdout:', repr(stdout)
+            print 'stderr:', repr(stderr)
+        return stdout, stderr
+
+
+def _get_tmp_dir():
+    return tempfile.mkdtemp(prefix='rc_integration_test')
+
+
+def _construct_url(repo, dest=None, **kwargs):
+    if dest is None:
+        #make temp clone
+        dest = _get_tmp_dir()
+    params = {
+        'user': TEST_USER_ADMIN_LOGIN,
+        'passwd': TEST_USER_ADMIN_PASS,
+        'host': HOST,
+        'cloned_repo': repo,
+        'dest': dest
+    }
+    params.update(**kwargs)
+    if params['user'] and params['passwd']:
+        _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s %(dest)s' % params
+    else:
+        _url = 'http://(host)s/%(cloned_repo)s %(dest)s' % params
+    return _url
+
+
+def _add_files_and_push(vcs, DEST, **kwargs):
+    """
+    Generate some files, add it to DEST repo and push back
+    vcs is git or hg and defines what VCS we want to make those files for
+
+    :param vcs:
+    :param DEST:
+    """
+    # commit some stuff into this repo
+    cwd = path = jn(DEST)
+    #added_file = jn(path, '%ssetupążźć.py' % _RandomNameSequence().next())
+    added_file = jn(path, '%ssetup.py' % _RandomNameSequence().next())
+    Command(cwd).execute('touch %s' % added_file)
+    Command(cwd).execute('%s add %s' % (vcs, added_file))
+
+    for i in xrange(kwargs.get('files_no', 3)):
+        cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
+        Command(cwd).execute(cmd)
+        author_str = 'User ǝɯɐᴎ <me@email.com>'
+        if vcs == 'hg':
+            cmd = """hg commit -m 'commited new %s' -u '%s' %s """ % (
+                i, author_str, added_file
+            )
+        elif vcs == 'git':
+            cmd = """EMAIL="me@email.com" git commit -m 'commited new %s' --author '%s' %s """ % (
+                i, author_str, added_file
+            )
+        Command(cwd).execute(cmd)
+
+    # PUSH it back
+    _REPO = None
+    if vcs == 'hg':
+        _REPO = HG_REPO
+    elif vcs == 'git':
+        _REPO = GIT_REPO
+
+    kwargs['dest'] = ''
+    clone_url = _construct_url(_REPO, **kwargs)
+    if 'clone_url' in kwargs:
+        clone_url = kwargs['clone_url']
+    stdout = stderr = None
+    if vcs == 'hg':
+        stdout, stderr = Command(cwd).execute('hg push --verbose', clone_url)
+    elif vcs == 'git':
+        stdout, stderr = Command(cwd).execute('git push --verbose', clone_url + " master")
+
+    return stdout, stderr
+
+
+def set_anonymous_access(enable=True):
+    user = User.get_by_username(User.DEFAULT_USER)
+    user.active = enable
+    Session().add(user)
+    Session().commit()
+    print '\tanonymous access is now:', enable
+    if enable != User.get_by_username(User.DEFAULT_USER).active:
+        raise Exception('Cannot set anonymous access')
+
+
+#==============================================================================
+# TESTS
+#==============================================================================
+
+
+def _check_proper_git_push(stdout, stderr):
+    #WTF Git stderr is output ?!
+    assert 'fatal' not in stderr
+    assert 'rejected' not in stderr
+    assert 'Pushing to' in stderr
+    assert 'master -> master' in stderr
+
+
+class TestVCSOperations(BaseTestCase):
+
+    @classmethod
+    def setup_class(cls):
+        #DISABLE ANONYMOUS ACCESS
+        set_anonymous_access(False)
+
+    def setUp(self):
+        r = Repository.get_by_repo_name(GIT_REPO)
+        Repository.unlock(r)
+        r.enable_locking = False
+        Session().add(r)
+        Session().commit()
+
+        r = Repository.get_by_repo_name(HG_REPO)
+        Repository.unlock(r)
+        r.enable_locking = False
+        Session().add(r)
+        Session().commit()
+
+    def test_clone_hg_repo_by_admin(self):
+        clone_url = _construct_url(HG_REPO)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        assert 'requesting all changes' in stdout
+        assert 'adding changesets' in stdout
+        assert 'adding manifests' in stdout
+        assert 'adding file changes' in stdout
+
+        assert stderr == ''
+
+    def test_clone_git_repo_by_admin(self):
+        clone_url = _construct_url(GIT_REPO)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        assert 'Cloning into' in stdout + stderr
+        assert stderr == '' or stdout == ''
+
+    def test_clone_wrong_credentials_hg(self):
+        clone_url = _construct_url(HG_REPO, passwd='bad!')
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+        assert 'abort: authorization failed' in stderr
+
+    def test_clone_wrong_credentials_git(self):
+        clone_url = _construct_url(GIT_REPO, passwd='bad!')
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+        assert 'fatal: Authentication failed' in stderr
+
+    def test_clone_git_dir_as_hg(self):
+        clone_url = _construct_url(GIT_REPO)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+        assert 'HTTP Error 404: Not Found' in stderr
+
+    def test_clone_hg_repo_as_git(self):
+        clone_url = _construct_url(HG_REPO)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+        assert 'not found' in stderr
+
+    def test_clone_non_existing_path_hg(self):
+        clone_url = _construct_url('trololo')
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+        assert 'HTTP Error 404: Not Found' in stderr
+
+    def test_clone_non_existing_path_git(self):
+        clone_url = _construct_url('trololo')
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+        assert 'not found' in stderr
+
+    def test_push_new_file_hg(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('hg', DEST)
+
+        assert 'pushing to' in stdout
+        assert 'Repository size' in stdout
+        assert 'Last revision is now' in stdout
+
+    def test_push_new_file_git(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        # commit some stuff into this repo
+        stdout, stderr = _add_files_and_push('git', DEST)
+
+        print [(x.repo_full_path,x.repo_path) for x in Repository.get_all()]
+        _check_proper_git_push(stdout, stderr)
+
+    def test_push_invalidates_cache_hg(self):
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               ==HG_REPO).scalar()
+        if not key:
+            key = CacheInvalidation(HG_REPO, HG_REPO)
+
+        key.cache_active = True
+        Session().add(key)
+        Session().commit()
+
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('hg', DEST, files_no=1)
+
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               ==HG_REPO).one()
+        self.assertEqual(key.cache_active, False)
+
+    def test_push_invalidates_cache_git(self):
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               ==GIT_REPO).scalar()
+        if not key:
+            key = CacheInvalidation(GIT_REPO, GIT_REPO)
+
+        key.cache_active = True
+        Session().add(key)
+        Session().commit()
+
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        # commit some stuff into this repo
+        stdout, stderr = _add_files_and_push('git', DEST, files_no=1)
+        _check_proper_git_push(stdout, stderr)
+
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               ==GIT_REPO).one()
+        print CacheInvalidation.get_all()
+        self.assertEqual(key.cache_active, False)
+
+    def test_push_wrong_credentials_hg(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('hg', DEST, user='bad',
+                                             passwd='name')
+
+        assert 'abort: authorization failed' in stderr
+
+    def test_push_wrong_credentials_git(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('git', DEST, user='bad',
+                                             passwd='name')
+
+        assert 'fatal: Authentication failed' in stderr
+
+    def test_push_back_to_wrong_url_hg(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('hg', DEST,
+                                    clone_url='http://127.0.0.1:5000/tmp',)
+
+        assert 'HTTP Error 404: Not Found' in stderr
+
+    def test_push_back_to_wrong_url_git(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('git', DEST,
+                                    clone_url='http://127.0.0.1:5000/tmp',)
+
+        assert 'not found' in stderr
+
+    def test_clone_and_create_lock_hg(self):
+        # enable locking
+        r = Repository.get_by_repo_name(HG_REPO)
+        r.enable_locking = True
+        Session().add(r)
+        Session().commit()
+        # clone
+        clone_url = _construct_url(HG_REPO)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        #check if lock was made
+        r = Repository.get_by_repo_name(HG_REPO)
+        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
+
+    def test_clone_and_create_lock_git(self):
+        # enable locking
+        r = Repository.get_by_repo_name(GIT_REPO)
+        r.enable_locking = True
+        Session().add(r)
+        Session().commit()
+        # clone
+        clone_url = _construct_url(GIT_REPO)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        #check if lock was made
+        r = Repository.get_by_repo_name(GIT_REPO)
+        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
+
+    def test_clone_after_repo_was_locked_hg(self):
+        #lock repo
+        r = Repository.get_by_repo_name(HG_REPO)
+        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
+        #pull fails since repo is locked
+        clone_url = _construct_url(HG_REPO)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
+                % (HG_REPO, TEST_USER_ADMIN_LOGIN))
+        assert msg in stderr
+
+    def test_clone_after_repo_was_locked_git(self):
+        #lock repo
+        r = Repository.get_by_repo_name(GIT_REPO)
+        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
+        #pull fails since repo is locked
+        clone_url = _construct_url(GIT_REPO)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+        msg = ("""The requested URL returned error: 423""")
+        assert msg in stderr
+
+    def test_push_on_locked_repo_by_other_user_hg(self):
+        #clone some temp
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        #lock repo
+        r = Repository.get_by_repo_name(HG_REPO)
+        # let this user actually push !
+        RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
+                                          perm='repository.write')
+        Session().commit()
+        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
+
+        #push fails repo is locked by other user !
+        stdout, stderr = _add_files_and_push('hg', DEST,
+                                             user=TEST_USER_REGULAR_LOGIN,
+                                             passwd=TEST_USER_REGULAR_PASS)
+        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
+                % (HG_REPO, TEST_USER_ADMIN_LOGIN))
+        assert msg in stderr
+
+    def test_push_on_locked_repo_by_other_user_git(self):
+        #clone some temp
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        #lock repo
+        r = Repository.get_by_repo_name(GIT_REPO)
+        # let this user actually push !
+        RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
+                                          perm='repository.write')
+        Session().commit()
+        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
+
+        #push fails repo is locked by other user !
+        stdout, stderr = _add_files_and_push('git', DEST,
+                                             user=TEST_USER_REGULAR_LOGIN,
+                                             passwd=TEST_USER_REGULAR_PASS)
+        err = 'Repository `%s` locked by user `%s`' % (GIT_REPO, TEST_USER_ADMIN_LOGIN)
+        assert err in stderr
+
+        #TODO: fix this somehow later on Git, Git is stupid and even if we throw
+        #back 423 to it, it makes ANOTHER request and we fail there with 405 :/
+
+        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
+                % (GIT_REPO, TEST_USER_ADMIN_LOGIN))
+        #msg = "405 Method Not Allowed"
+        #assert msg in stderr
+
+    def test_push_unlocks_repository_hg(self):
+        # enable locking
+        r = Repository.get_by_repo_name(HG_REPO)
+        r.enable_locking = True
+        Session().add(r)
+        Session().commit()
+        #clone some temp
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        #check for lock repo after clone
+        r = Repository.get_by_repo_name(HG_REPO)
+        uid = User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
+        assert r.locked[0] == uid
+
+        #push is ok and repo is now unlocked
+        stdout, stderr = _add_files_and_push('hg', DEST)
+        assert ('remote: Released lock on repo `%s`' % HG_REPO) in stdout
+        #we need to cleanup the Session Here !
+        Session.remove()
+        r = Repository.get_by_repo_name(HG_REPO)
+        assert r.locked == [None, None]
+
+    #TODO: fix me ! somehow during tests hooks don't get called on Git
+    def test_push_unlocks_repository_git(self):
+        # enable locking
+        r = Repository.get_by_repo_name(GIT_REPO)
+        r.enable_locking = True
+        Session().add(r)
+        Session().commit()
+        #clone some temp
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        #check for lock repo after clone
+        r = Repository.get_by_repo_name(GIT_REPO)
+        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
+
+        #push is ok and repo is now unlocked
+        stdout, stderr = _add_files_and_push('git', DEST)
+        _check_proper_git_push(stdout, stderr)
+
+        #assert ('remote: Released lock on repo `%s`' % GIT_REPO) in stdout
+        #we need to cleanup the Session Here !
+        Session.remove()
+        r = Repository.get_by_repo_name(GIT_REPO)
+        assert r.locked == [None, None]
+
+    def test_ip_restriction_hg(self):
+        user_model = UserModel()
+        try:
+            user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
+            Session().commit()
+            clone_url = _construct_url(HG_REPO)
+            stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+            assert 'abort: HTTP Error 403: Forbidden' in stderr
+        finally:
+            #release IP restrictions
+            for ip in UserIpMap.getAll():
+                UserIpMap.delete(ip.ip_id)
+            Session().commit()
+
+        time.sleep(2)
+        clone_url = _construct_url(HG_REPO)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        assert 'requesting all changes' in stdout
+        assert 'adding changesets' in stdout
+        assert 'adding manifests' in stdout
+        assert 'adding file changes' in stdout
+
+        assert stderr == ''
+
+    def test_ip_restriction_git(self):
+        user_model = UserModel()
+        try:
+            user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
+            Session().commit()
+            clone_url = _construct_url(GIT_REPO)
+            stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+            msg = ("""The requested URL returned error: 403""")
+            assert msg in stderr
+        finally:
+            #release IP restrictions
+            for ip in UserIpMap.getAll():
+                UserIpMap.delete(ip.ip_id)
+            Session().commit()
+
+        time.sleep(2)
+        clone_url = _construct_url(GIT_REPO)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        assert 'Cloning into' in stdout + stderr
+        assert stderr == '' or stdout == ''
--- a/kallithea/tests/other/test_libs.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/other/test_libs.py	Mon Jul 20 15:07:54 2015 +0200
@@ -37,9 +37,9 @@
 TEST_URLS = [
     ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
-    ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
+    ('%s://username@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
-    ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
+    ('%s://username:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
     ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
      '%s://127.0.0.1:8080' % proto),
@@ -54,9 +54,9 @@
 TEST_URLS += [
     ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
-    ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
+    ('%s://username@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
-    ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
+    ('%s://username:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
     ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
      '%s://127.0.0.1:8080' % proto),
@@ -105,16 +105,16 @@
     def test_mention_extractor(self):
         from kallithea.lib.utils2 import extract_mentioned_users
         sample = (
-            "@first hi there @marcink here's my email marcin@email.com "
+            "@first hi there @world here's my email username@email.com "
             "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three "
-            "@MARCIN    @maRCiN @2one_more22 @john please see this http://org.pl "
+            "@UPPER    @cAmEL @2one_more22 @john please see this http://org.pl "
             "@marian.user just do it @marco-polo and next extract @marco_polo "
             "user.dot  hej ! not-needed maril@domain.org"
         )
 
         s = sorted([
-            '2one_more22', 'first', 'marcink', 'lukaszb', 'one', 'one_more22', 'MARCIN', 'maRCiN', 'john',
-            'marian.user', 'marco-polo', 'marco_polo'], key=lambda k: k.lower())
+            '2one_more22', 'first', 'lukaszb', 'one', 'one_more22', 'UPPER', 'cAmEL', 'john',
+            'marian.user', 'marco-polo', 'marco_polo', 'world'], key=lambda k: k.lower())
         self.assertEqual(s, extract_mentioned_users(sample))
 
     @parameterized.expand([
@@ -189,14 +189,14 @@
             "[requires => url] [lang => python] [just a tag]"
             "[,d] [ => ULR ] [obsolete] [desc]]"
         )
-        from kallithea.lib.helpers import desc_stylize
-        res = desc_stylize(sample)
-        self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
-        self.assertTrue('<div class="metatag" tag="obsolete">obsolete</div>' in res)
-        self.assertTrue('<div class="metatag" tag="stale">stale</div>' in res)
-        self.assertTrue('<div class="metatag" tag="lang">python</div>' in res)
-        self.assertTrue('<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res)
-        self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
+        from kallithea.lib.helpers import urlify_text
+        res = urlify_text(sample, stylize=True)
+        self.assertIn('<div class="metatag" tag="tag">tag</div>', res)
+        self.assertIn('<div class="metatag" tag="obsolete">obsolete</div>', res)
+        self.assertIn('<div class="metatag" tag="stale">stale</div>', res)
+        self.assertIn('<div class="metatag" tag="lang">python</div>', res)
+        self.assertIn('<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>', res)
+        self.assertIn('<div class="metatag" tag="tag">tag</div>', res)
 
     def test_alternative_gravatar(self):
         from kallithea.lib.helpers import gravatar_url
@@ -251,23 +251,23 @@
 
     @parameterized.expand([
         (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '', 'http://vps1:8000/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '/rc', 'http://vps1:8000/rc/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc', 'http://user@vps1:8000/rc/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc', 'http://marcink@vps1:8000/rc/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc/', 'http://user@vps1:8000/rc/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc/', 'http://marcink@vps1:8000/rc/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'username'}, '', 'http://username@vps1:8000/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '/prefix', 'http://vps1:8000/prefix/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/prefix', 'http://user@vps1:8000/prefix/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'username'}, '/prefix', 'http://username@vps1:8000/prefix/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/prefix/', 'http://user@vps1:8000/prefix/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'username'}, '/prefix/', 'http://username@vps1:8000/prefix/group/repo1'),
         ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {}, '', 'http://vps1:8000/_23'),
-        ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
-        ('http://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
-        ('http://{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://vps1:8000/_23'),
-        ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://marcink@proxy1.server.com/group/repo1'),
+        ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'username'}, '', 'http://username@vps1:8000/_23'),
+        ('http://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'username'}, '', 'http://username@vps1:8000/_23'),
+        ('http://{netloc}/_{repoid}', 'group/repo1', {'user': 'username'}, '', 'http://vps1:8000/_23'),
+        ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {'user': 'username'}, '', 'https://username@proxy1.server.com/group/repo1'),
         ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {}, '', 'https://proxy1.server.com/group/repo1'),
-        ('https://proxy1.server.com/{user}/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://proxy1.server.com/marcink/group/repo1'),
+        ('https://proxy1.server.com/{user}/{repo}', 'group/repo1', {'user': 'username'}, '', 'https://proxy1.server.com/username/group/repo1'),
     ])
     def test_clone_url_generator(self, tmpl, repo_name, overrides, prefix, expected):
         from kallithea.lib.utils2 import get_clone_url
-        clone_url = get_clone_url(uri_tmpl=tmpl, qualifed_home_url='http://vps1:8000'+prefix,
+        clone_url = get_clone_url(uri_tmpl=tmpl, qualified_home_url='http://vps1:8000'+prefix,
                                   repo_name=repo_name, repo_id=23, **overrides)
         self.assertEqual(clone_url, expected)
 
@@ -366,7 +366,7 @@
       ("/_21/foobar", '21'),
       ("_21/121", '21'),
       ("/_21/_12", '21'),
-      ("_21/rc/foo", '21'),
+      ("_21/prefix/foo", '21'),
     ])
     def test_get_repo_by_id(self, test, expected):
         from kallithea.lib.utils import _extract_id_from_repo_name
--- a/kallithea/tests/other/test_validators.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/other/test_validators.py	Mon Jul 20 15:07:54 2015 +0200
@@ -8,7 +8,7 @@
 
 from kallithea.model.meta import Session
 from kallithea.model.repo_group import RepoGroupModel
-from kallithea.model.db import ChangesetStatus, Repository
+from kallithea.model.db import ChangesetStatus, ChangesetComment, Repository
 from kallithea.model.changeset_status import ChangesetStatusModel
 from kallithea.tests.fixture import Fixture
 
@@ -233,16 +233,23 @@
         self.assertEqual('DN_attr', validator.to_python('DN_attr'))
 
     def test_NotReviewedRevisions(self):
-        repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
-        validator = v.NotReviewedRevisions(repo_id)
+        user = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN)
+        repo = Repository.get_by_repo_name(HG_REPO)
+        validator = v.NotReviewedRevisions(repo.repo_id)
         rev = '0' * 40
         # add status for a rev, that should throw an error because it is already
         # reviewed
+        new_comment = ChangesetComment()
+        new_comment.repo = repo
+        new_comment.author = user
+        new_comment.text = u''
+        Session().add(new_comment)
+        Session().flush()
         new_status = ChangesetStatus()
-        new_status.author = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN)
-        new_status.repo = ChangesetStatusModel()._get_repo(HG_REPO)
+        new_status.author = user
+        new_status.repo = repo
         new_status.status = ChangesetStatus.STATUS_APPROVED
-        new_status.comment = None
+        new_status.comment = new_comment
         new_status.revision = rev
         Session().add(new_status)
         Session().commit()
--- a/kallithea/tests/other/test_vcs_operations.py	Mon Jul 20 15:07:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,533 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-kallithea.tests.test_scm_operations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Test suite for making push/pull operations.
-Run using after doing paster serve test.ini::
- KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 nosetests kallithea/tests/other/test_vcs_operations.py
-
-You must have git > 1.8.1 for tests to work fine
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Dec 30, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-
-"""
-
-import tempfile
-import time
-from os.path import join as jn
-
-from tempfile import _RandomNameSequence
-from subprocess import Popen, PIPE
-
-from kallithea.tests import *
-from kallithea.model.db import User, Repository, UserIpMap, CacheInvalidation
-from kallithea.model.meta import Session
-from kallithea.model.repo import RepoModel
-from kallithea.model.user import UserModel
-
-DEBUG = True
-HOST = '127.0.0.1:5000'  # test host
-
-
-class Command(object):
-
-    def __init__(self, cwd):
-        self.cwd = cwd
-
-    def execute(self, cmd, *args):
-        """
-        Runs command on the system with given ``args``.
-        """
-
-        command = cmd + ' ' + ' '.join(args)
-        if DEBUG:
-            print '*** CMD %s ***' % command
-        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
-        stdout, stderr = p.communicate()
-        if DEBUG:
-            print stdout, stderr
-        return stdout, stderr
-
-
-def _get_tmp_dir():
-    return tempfile.mkdtemp(prefix='rc_integration_test')
-
-
-def _construct_url(repo, dest=None, **kwargs):
-    if dest is None:
-        #make temp clone
-        dest = _get_tmp_dir()
-    params = {
-        'user': TEST_USER_ADMIN_LOGIN,
-        'passwd': TEST_USER_ADMIN_PASS,
-        'host': HOST,
-        'cloned_repo': repo,
-        'dest': dest
-    }
-    params.update(**kwargs)
-    if params['user'] and params['passwd']:
-        _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s %(dest)s' % params
-    else:
-        _url = 'http://(host)s/%(cloned_repo)s %(dest)s' % params
-    return _url
-
-
-def _add_files_and_push(vcs, DEST, **kwargs):
-    """
-    Generate some files, add it to DEST repo and push back
-    vcs is git or hg and defines what VCS we want to make those files for
-
-    :param vcs:
-    :param DEST:
-    """
-    # commit some stuff into this repo
-    cwd = path = jn(DEST)
-    #added_file = jn(path, '%ssetupążźć.py' % _RandomNameSequence().next())
-    added_file = jn(path, '%ssetup.py' % _RandomNameSequence().next())
-    Command(cwd).execute('touch %s' % added_file)
-    Command(cwd).execute('%s add %s' % (vcs, added_file))
-
-    for i in xrange(kwargs.get('files_no', 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, author_str, added_file
-            )
-        elif vcs == 'git':
-            cmd = """EMAIL="me@email.com" git commit -m 'commited new %s' --author '%s' %s """ % (
-                i, author_str, added_file
-            )
-        Command(cwd).execute(cmd)
-
-    # PUSH it back
-    _REPO = None
-    if vcs == 'hg':
-        _REPO = HG_REPO
-    elif vcs == 'git':
-        _REPO = GIT_REPO
-
-    kwargs['dest'] = ''
-    clone_url = _construct_url(_REPO, **kwargs)
-    if 'clone_url' in kwargs:
-        clone_url = kwargs['clone_url']
-    stdout = stderr = None
-    if vcs == 'hg':
-        stdout, stderr = Command(cwd).execute('hg push --verbose', clone_url)
-    elif vcs == 'git':
-        stdout, stderr = Command(cwd).execute('git push --verbose', clone_url + " master")
-
-    return stdout, stderr
-
-
-def set_anonymous_access(enable=True):
-    user = User.get_by_username(User.DEFAULT_USER)
-    user.active = enable
-    Session().add(user)
-    Session().commit()
-    print '\tanonymous access is now:', enable
-    if enable != User.get_by_username(User.DEFAULT_USER).active:
-        raise Exception('Cannot set anonymous access')
-
-
-#==============================================================================
-# TESTS
-#==============================================================================
-
-
-def _check_proper_git_push(stdout, stderr):
-    #WTF Git stderr is output ?!
-    assert 'fatal' not in stderr
-    assert 'rejected' not in stderr
-    assert 'Pushing to' in stderr
-    assert 'master -> master' in stderr
-
-
-class TestVCSOperations(BaseTestCase):
-
-    @classmethod
-    def setup_class(cls):
-        #DISABLE ANONYMOUS ACCESS
-        set_anonymous_access(False)
-
-    def setUp(self):
-        r = Repository.get_by_repo_name(GIT_REPO)
-        Repository.unlock(r)
-        r.enable_locking = False
-        Session().add(r)
-        Session().commit()
-
-        r = Repository.get_by_repo_name(HG_REPO)
-        Repository.unlock(r)
-        r.enable_locking = False
-        Session().add(r)
-        Session().commit()
-
-    def test_clone_hg_repo_by_admin(self):
-        clone_url = _construct_url(HG_REPO)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        assert 'requesting all changes' in stdout
-        assert 'adding changesets' in stdout
-        assert 'adding manifests' in stdout
-        assert 'adding file changes' in stdout
-
-        assert stderr == ''
-
-    def test_clone_git_repo_by_admin(self):
-        clone_url = _construct_url(GIT_REPO)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        assert 'Cloning into' in stdout
-        assert stderr == ''
-
-    def test_clone_wrong_credentials_hg(self):
-        clone_url = _construct_url(HG_REPO, passwd='bad!')
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-        assert 'abort: authorization failed' in stderr
-
-    def test_clone_wrong_credentials_git(self):
-        clone_url = _construct_url(GIT_REPO, passwd='bad!')
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-        assert 'fatal: Authentication failed' in stderr
-
-    def test_clone_git_dir_as_hg(self):
-        clone_url = _construct_url(GIT_REPO)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-        assert 'HTTP Error 404: Not Found' in stderr
-
-    def test_clone_hg_repo_as_git(self):
-        clone_url = _construct_url(HG_REPO)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-        assert 'not found' in stderr
-
-    def test_clone_non_existing_path_hg(self):
-        clone_url = _construct_url('trololo')
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-        assert 'HTTP Error 404: Not Found' in stderr
-
-    def test_clone_non_existing_path_git(self):
-        clone_url = _construct_url('trololo')
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-        assert 'not found' in stderr
-
-    def test_push_new_file_hg(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('hg', DEST)
-
-        assert 'pushing to' in stdout
-        assert 'Repository size' in stdout
-        assert 'Last revision is now' in stdout
-
-    def test_push_new_file_git(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        # commit some stuff into this repo
-        stdout, stderr = _add_files_and_push('git', DEST)
-
-        print [(x.repo_full_path,x.repo_path) for x in Repository.get_all()]
-        _check_proper_git_push(stdout, stderr)
-
-    def test_push_invalidates_cache_hg(self):
-        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
-                                               ==HG_REPO).scalar()
-        if not key:
-            key = CacheInvalidation(HG_REPO, HG_REPO)
-
-        key.cache_active = True
-        Session().add(key)
-        Session().commit()
-
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('hg', DEST, files_no=1)
-
-        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
-                                               ==HG_REPO).one()
-        self.assertEqual(key.cache_active, False)
-
-    def test_push_invalidates_cache_git(self):
-        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
-                                               ==GIT_REPO).scalar()
-        if not key:
-            key = CacheInvalidation(GIT_REPO, GIT_REPO)
-
-        key.cache_active = True
-        Session().add(key)
-        Session().commit()
-
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        # commit some stuff into this repo
-        stdout, stderr = _add_files_and_push('git', DEST, files_no=1)
-        _check_proper_git_push(stdout, stderr)
-
-        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
-                                               ==GIT_REPO).one()
-        print CacheInvalidation.get_all()
-        self.assertEqual(key.cache_active, False)
-
-    def test_push_wrong_credentials_hg(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('hg', DEST, user='bad',
-                                             passwd='name')
-
-        assert 'abort: authorization failed' in stderr
-
-    def test_push_wrong_credentials_git(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('git', DEST, user='bad',
-                                             passwd='name')
-
-        assert 'fatal: Authentication failed' in stderr
-
-    def test_push_back_to_wrong_url_hg(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('hg', DEST,
-                                    clone_url='http://127.0.0.1:5000/tmp',)
-
-        assert 'HTTP Error 404: Not Found' in stderr
-
-    def test_push_back_to_wrong_url_git(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('git', DEST,
-                                    clone_url='http://127.0.0.1:5000/tmp',)
-
-        assert 'not found' in stderr
-
-    def test_clone_and_create_lock_hg(self):
-        # enable locking
-        r = Repository.get_by_repo_name(HG_REPO)
-        r.enable_locking = True
-        Session().add(r)
-        Session().commit()
-        # clone
-        clone_url = _construct_url(HG_REPO)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        #check if lock was made
-        r = Repository.get_by_repo_name(HG_REPO)
-        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
-
-    def test_clone_and_create_lock_git(self):
-        # enable locking
-        r = Repository.get_by_repo_name(GIT_REPO)
-        r.enable_locking = True
-        Session().add(r)
-        Session().commit()
-        # clone
-        clone_url = _construct_url(GIT_REPO)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        #check if lock was made
-        r = Repository.get_by_repo_name(GIT_REPO)
-        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
-
-    def test_clone_after_repo_was_locked_hg(self):
-        #lock repo
-        r = Repository.get_by_repo_name(HG_REPO)
-        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
-        #pull fails since repo is locked
-        clone_url = _construct_url(HG_REPO)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
-                % (HG_REPO, TEST_USER_ADMIN_LOGIN))
-        assert msg in stderr
-
-    def test_clone_after_repo_was_locked_git(self):
-        #lock repo
-        r = Repository.get_by_repo_name(GIT_REPO)
-        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
-        #pull fails since repo is locked
-        clone_url = _construct_url(GIT_REPO)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-        msg = ("""The requested URL returned error: 423""")
-        assert msg in stderr
-
-    def test_push_on_locked_repo_by_other_user_hg(self):
-        #clone some temp
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        #lock repo
-        r = Repository.get_by_repo_name(HG_REPO)
-        # let this user actually push !
-        RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
-                                          perm='repository.write')
-        Session().commit()
-        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
-
-        #push fails repo is locked by other user !
-        stdout, stderr = _add_files_and_push('hg', DEST,
-                                             user=TEST_USER_REGULAR_LOGIN,
-                                             passwd=TEST_USER_REGULAR_PASS)
-        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
-                % (HG_REPO, TEST_USER_ADMIN_LOGIN))
-        assert msg in stderr
-
-    def test_push_on_locked_repo_by_other_user_git(self):
-        #clone some temp
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        #lock repo
-        r = Repository.get_by_repo_name(GIT_REPO)
-        # let this user actually push !
-        RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
-                                          perm='repository.write')
-        Session().commit()
-        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
-
-        #push fails repo is locked by other user !
-        stdout, stderr = _add_files_and_push('git', DEST,
-                                             user=TEST_USER_REGULAR_LOGIN,
-                                             passwd=TEST_USER_REGULAR_PASS)
-        err = 'Repository `%s` locked by user `%s`' % (GIT_REPO, TEST_USER_ADMIN_LOGIN)
-        assert err in stderr
-
-        #TODO: fix this somehow later on Git, Git is stupid and even if we throw
-        #back 423 to it, it makes ANOTHER request and we fail there with 405 :/
-
-        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
-                % (GIT_REPO, TEST_USER_ADMIN_LOGIN))
-        #msg = "405 Method Not Allowed"
-        #assert msg in stderr
-
-    def test_push_unlocks_repository_hg(self):
-        # enable locking
-        r = Repository.get_by_repo_name(HG_REPO)
-        r.enable_locking = True
-        Session().add(r)
-        Session().commit()
-        #clone some temp
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        #check for lock repo after clone
-        r = Repository.get_by_repo_name(HG_REPO)
-        uid = User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
-        assert r.locked[0] == uid
-
-        #push is ok and repo is now unlocked
-        stdout, stderr = _add_files_and_push('hg', DEST)
-        assert ('remote: Released lock on repo `%s`' % HG_REPO) in stdout
-        #we need to cleanup the Session Here !
-        Session.remove()
-        r = Repository.get_by_repo_name(HG_REPO)
-        assert r.locked == [None, None]
-
-    #TODO: fix me ! somehow during tests hooks don't get called on Git
-    def test_push_unlocks_repository_git(self):
-        # enable locking
-        r = Repository.get_by_repo_name(GIT_REPO)
-        r.enable_locking = True
-        Session().add(r)
-        Session().commit()
-        #clone some temp
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        #check for lock repo after clone
-        r = Repository.get_by_repo_name(GIT_REPO)
-        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
-
-        #push is ok and repo is now unlocked
-        stdout, stderr = _add_files_and_push('git', DEST)
-        _check_proper_git_push(stdout, stderr)
-
-        #assert ('remote: Released lock on repo `%s`' % GIT_REPO) in stdout
-        #we need to cleanup the Session Here !
-        Session.remove()
-        r = Repository.get_by_repo_name(GIT_REPO)
-        assert r.locked == [None, None]
-
-    def test_ip_restriction_hg(self):
-        user_model = UserModel()
-        try:
-            user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
-            Session().commit()
-            clone_url = _construct_url(HG_REPO)
-            stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-            assert 'abort: HTTP Error 403: Forbidden' in stderr
-        finally:
-            #release IP restrictions
-            for ip in UserIpMap.getAll():
-                UserIpMap.delete(ip.ip_id)
-            Session().commit()
-
-        time.sleep(2)
-        clone_url = _construct_url(HG_REPO)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        assert 'requesting all changes' in stdout
-        assert 'adding changesets' in stdout
-        assert 'adding manifests' in stdout
-        assert 'adding file changes' in stdout
-
-        assert stderr == ''
-
-    def test_ip_restriction_git(self):
-        user_model = UserModel()
-        try:
-            user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
-            Session().commit()
-            clone_url = _construct_url(GIT_REPO)
-            stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-            msg = ("""The requested URL returned error: 403""")
-            assert msg in stderr
-        finally:
-            #release IP restrictions
-            for ip in UserIpMap.getAll():
-                UserIpMap.delete(ip.ip_id)
-            Session().commit()
-
-        time.sleep(2)
-        clone_url = _construct_url(GIT_REPO)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        assert 'Cloning into' in stdout
-        assert stderr == ''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/parameterized.py	Mon Jul 20 15:07:54 2015 +0200
@@ -0,0 +1,241 @@
+import re
+import new
+import inspect
+import logging
+import logging.handlers
+from functools import wraps
+
+from unittest import TestCase
+
+
+def skip_test(func):
+    try:
+        from nose.tools import nottest
+    except ImportError:
+        pass
+    else:
+        func = nottest(func)
+
+    try:
+        import pytest
+    except ImportError:
+        pass
+    else:
+        func = pytest.mark.skipIf(True, func)
+
+    return func
+
+
+def _terrible_magic_get_defining_classes():
+    """ Returns the set of parent classes of the class currently being defined.
+        Will likely only work if called from the ``parameterized`` decorator.
+        This function is entirely @brandon_rhodes's fault, as he suggested
+        the implementation: http://stackoverflow.com/a/8793684/71522
+        """
+    stack = inspect.stack()
+    if len(stack) <= 4:
+        return []
+    frame = stack[3]
+    code_context = frame[4][0].strip()
+    if not code_context.startswith("class "):
+        return []
+    _, parents = code_context.split("(", 1)
+    parents, _ = parents.rsplit(")", 1)
+    return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals)
+
+
+def parameterized(input):
+    """ Parameterize a test case:
+        >>> add1_tests = [(1, 2), (2, 3)]
+        >>> class TestFoo(object):
+        ...     @parameterized(add1_tests)
+        ...     def test_add1(self, input, expected):
+        ...         assert_equal(add1(input), expected)
+        >>> @parameterized(add1_tests)
+        ... def test_add1(input, expected):
+        ...     assert_equal(add1(input), expected)
+        >>>
+        """
+
+    if not hasattr(input, "__iter__"):
+        raise ValueError("expected iterable input; got %r" % (input,))
+
+    def parameterized_helper(f):
+        attached_instance_method = [False]
+
+        parent_classes = _terrible_magic_get_defining_classes()
+        if any(issubclass(cls, TestCase) for cls in parent_classes):
+            raise Exception("Warning: '@parameterized' tests won't work "
+                            "inside subclasses of 'TestCase' - use "
+                            "'@parameterized.expand' instead")
+
+        @wraps(f)
+        def parameterized_helper_method(self=None):
+            if self is not None and not attached_instance_method[0]:
+                # confusingly, we need to create a named instance method and
+                # attach that to the class...
+                cls = self.__class__
+                im_f = new.instancemethod(f, None, cls)
+                setattr(cls, f.__name__, im_f)
+                attached_instance_method[0] = True
+            for args in input:
+                if isinstance(args, basestring):
+                    args = [args]
+                # ... then pull that named instance method off, turning it into
+                # a bound method ...
+                if self is not None:
+                    args = [getattr(self, f.__name__)] + list(args)
+                else:
+                    args = [f] + list(args)
+                # ... then yield that as a tuple. If those steps aren't
+                # followed precicely, Nose gets upset and doesn't run the test
+                # or doesn't run setup methods.
+                yield tuple(args)
+
+        f.__name__ = "_helper_for_%s" % (f.__name__,)
+        parameterized_helper_method.parameterized_input = input
+        parameterized_helper_method.parameterized_func = f
+        return parameterized_helper_method
+
+    return parameterized_helper
+
+
+def to_safe_name(s):
+    return re.sub("[^a-zA-Z0-9_]", "", s)
+
+
+def parameterized_expand_helper(func_name, func, args):
+    def parameterized_expand_helper_helper(self=()):
+        if self != ():
+            self = (self,)
+        return func(*(self + args))
+    parameterized_expand_helper_helper.__name__ = str(func_name)
+    return parameterized_expand_helper_helper
+
+
+def parameterized_expand(input):
+    """ A "brute force" method of parameterizing test cases. Creates new test
+        cases and injects them into the namespace that the wrapped function
+        is being defined in. Useful for parameterizing tests in subclasses
+        of 'UnitTest', where Nose test generators don't work.
+
+        >>> @parameterized.expand([("foo", 1, 2)])
+        ... def test_add1(name, input, expected):
+        ...     actual = add1(input)
+        ...     assert_equal(actual, expected)
+        ...
+        >>> locals()
+        ... 'test_add1_foo_0': <function ...> ...
+        >>>
+        """
+
+    def parameterized_expand_wrapper(f):
+        stack = inspect.stack()
+        frame = stack[1]
+        frame_locals = frame[0].f_locals
+
+        base_name = f.__name__
+        for num, args in enumerate(input):
+            name_suffix = "_%s" % (num,)
+            if len(args) > 0 and isinstance(args[0], basestring):
+                name_suffix += "_" + to_safe_name(args[0])
+            name = base_name + name_suffix
+            new_func = parameterized_expand_helper(name, f, args)
+            frame_locals[name] = new_func
+        return skip_test(f)
+    return parameterized_expand_wrapper
+
+parameterized.expand = parameterized_expand
+
+
+def assert_contains(haystack, needle):
+    if needle not in haystack:
+        raise AssertionError("%r not in %r" % (needle, haystack))
+
+
+def assert_not_contains(haystack, needle):
+    if needle in haystack:
+        raise AssertionError("%r in %r" % (needle, haystack))
+
+
+def assert_raises(func, exc_type, str_contains=None, repr_contains=None):
+    try:
+        func()
+    except exc_type, e:
+        if str_contains is not None and str_contains not in str(e):
+            raise AssertionError("%s raised, but %r does not contain %r"
+                                 % (exc_type, str(e), str_contains))
+        if repr_contains is not None and repr_contains not in repr(e):
+            raise AssertionError("%s raised, but %r does not contain %r"
+                                 % (exc_type, repr(e), repr_contains))
+        return e
+    else:
+        raise AssertionError("%s not raised" % (exc_type,))
+
+
+log_handler = None
+
+
+def setup_logging():
+    """ Configures a log handler which will capure log messages during a test.
+        The ``logged_messages`` and ``assert_no_errors_logged`` functions can be
+        used to make assertions about these logged messages.
+
+        For example::
+
+            from ensi_common.testing import (
+                setup_logging, teardown_logging, assert_no_errors_logged,
+                assert_logged,
+            )
+
+            class TestWidget(object):
+                def setup(self):
+                    setup_logging()
+
+                def teardown(self):
+                    assert_no_errors_logged()
+                    teardown_logging()
+
+                def test_that_will_fail(self):
+                    log.warning("this warning message will trigger a failure")
+
+                def test_that_will_pass(self):
+                    log.info("but info messages are ok")
+                    assert_logged("info messages are ok")
+        """
+
+    global log_handler
+    if log_handler is not None:
+        logging.getLogger().removeHandler(log_handler)
+    log_handler = logging.handlers.BufferingHandler(1000)
+    formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
+    log_handler.setFormatter(formatter)
+    logging.getLogger().addHandler(log_handler)
+
+
+def teardown_logging():
+    global log_handler
+    if log_handler is not None:
+        logging.getLogger().removeHandler(log_handler)
+        log_handler = None
+
+
+def logged_messages():
+    assert log_handler, "setup_logging not called"
+    return [(log_handler.format(record), record) for record in log_handler.buffer]
+
+
+def assert_no_errors_logged():
+    for _, record in logged_messages():
+        if record.levelno >= logging.WARNING:
+            # Assume that the nose log capture plugin is being used, so it will
+            # show the exception.
+            raise AssertionError("an unexpected error was logged")
+
+
+def assert_logged(expected_msg_contents):
+    for msg, _ in logged_messages():
+        if expected_msg_contents in msg:
+            return
+    raise AssertionError("no logged message contains %r"
+                         % (expected_msg_contents,))
--- a/kallithea/tests/scripts/create_rc.sh	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/scripts/create_rc.sh	Mon Jul 20 15:07:54 2015 +0200
@@ -1,10 +1,10 @@
 #!/bin/sh
 psql -U postgres -h localhost -c 'drop database if exists kallithea;'
 psql -U postgres -h localhost -c 'create database kallithea;'
-paster setup-db rc.ini --force-yes --user=username --password=qweqwe --email=username@example.com --repos=/home/username/repos --no-public-access
+paster setup-db server.ini --force-yes --user=username --password=qweqwe --email=username@example.com --repos=/home/username/repos --no-public-access
 API_KEY=`psql -R " " -A -U postgres -h localhost -c "select api_key from users where admin=TRUE" -d kallithea | awk '{print $2}'`
 echo "run those after running server"
-paster serve rc.ini --pid-file=rc.pid --daemon
+paster serve server.ini --pid-file=server.pid --daemon
 sleep 3
 kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo1 password:qweqwe email:demo1@example.com
 kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo2 password:qweqwe email:demo2@example.com
@@ -13,5 +13,5 @@
 kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_user_group usergroupid:demo12 userid:demo1
 kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_user_group usergroupid:demo12 userid:demo2
 echo "killing server"
-kill `cat rc.pid`
-rm rc.pid
+kill `cat server.pid`
+rm server.pid
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/scripts/manual_test_concurrency.py	Mon Jul 20 15:07:54 2015 +0200
@@ -0,0 +1,222 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.tests.test_hg_operations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Test suite for making push/pull operations
+
+This file was forked by the Kallithea project in July 2014.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: Dec 30, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+
+"""
+
+import os
+import sys
+import shutil
+import logging
+from os.path import join as jn
+from os.path import dirname as dn
+
+from tempfile import _RandomNameSequence
+from subprocess import Popen, PIPE
+
+from paste.deploy import appconfig
+from sqlalchemy import engine_from_config
+
+from kallithea.lib.utils import add_cache
+from kallithea.model import init_model
+from kallithea.model import meta
+from kallithea.model.db import User, Repository
+from kallithea.lib.auth import get_crypt_password
+
+from kallithea.tests import TESTS_TMP_PATH, HG_REPO
+from kallithea.config.environment import load_environment
+
+rel_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
+conf = appconfig('config:development.ini', relative_to=rel_path)
+load_environment(conf.global_conf, conf.local_conf)
+
+add_cache(conf)
+
+USER = TEST_USER_ADMIN_LOGIN
+PASS = TEST_USER_ADMIN_PASS
+HOST = 'server.local'
+METHOD = 'pull'
+DEBUG = True
+log = logging.getLogger(__name__)
+
+
+class Command(object):
+
+    def __init__(self, cwd):
+        self.cwd = cwd
+
+    def execute(self, cmd, *args):
+        """Runs command on the system with given ``args``.
+        """
+
+        command = cmd + ' ' + ' '.join(args)
+        log.debug('Executing %s' % command)
+        if DEBUG:
+            print command
+        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
+        stdout, stderr = p.communicate()
+        if DEBUG:
+            print stdout, stderr
+        return stdout, stderr
+
+
+def get_session():
+    engine = engine_from_config(conf, 'sqlalchemy.db1.')
+    init_model(engine)
+    sa = meta.Session
+    return sa
+
+
+def create_test_user(force=True):
+    print 'creating test user'
+    sa = get_session()
+
+    user = sa.query(User).filter(User.username == USER).scalar()
+
+    if force and user is not None:
+        print 'removing current user'
+        for repo in sa.query(Repository).filter(Repository.user == user).all():
+            sa.delete(repo)
+        sa.delete(user)
+        sa.commit()
+
+    if user is None or force:
+        print 'creating new one'
+        new_usr = User()
+        new_usr.username = USER
+        new_usr.password = get_crypt_password(PASS)
+        new_usr.email = 'mail@mail.com'
+        new_usr.name = 'test'
+        new_usr.lastname = 'lasttestname'
+        new_usr.active = True
+        new_usr.admin = True
+        sa.add(new_usr)
+        sa.commit()
+
+    print 'done'
+
+
+def create_test_repo(force=True):
+    print 'creating test repo'
+    from kallithea.model.repo import RepoModel
+    sa = get_session()
+
+    user = sa.query(User).filter(User.username == USER).scalar()
+    if user is None:
+        raise Exception('user not found')
+
+    repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar()
+
+    if repo is None:
+        print 'repo not found creating'
+
+        form_data = {'repo_name': HG_REPO,
+                     'repo_type': 'hg',
+                     'private':False,
+                     'clone_uri': '' }
+        rm = RepoModel(sa)
+        rm.base_path = '/home/hg'
+        rm.create(form_data, user)
+
+    print 'done'
+
+
+def set_anonymous_access(enable=True):
+    sa = get_session()
+    user = sa.query(User).filter(User.username == 'default').one()
+    user.active = enable
+    sa.add(user)
+    sa.commit()
+
+
+def get_anonymous_access():
+    sa = get_session()
+    return sa.query(User).filter(User.username == 'default').one().active
+
+
+#==============================================================================
+# TESTS
+#==============================================================================
+def test_clone_with_credentials(no_errors=False, repo=HG_REPO, method=METHOD,
+                                seq=None, backend='hg'):
+    cwd = path = jn(TESTS_TMP_PATH, repo)
+
+    if seq is None:
+        seq = _RandomNameSequence().next()
+
+    try:
+        shutil.rmtree(path, ignore_errors=True)
+        os.makedirs(path)
+        #print 'made dirs %s' % jn(path)
+    except OSError:
+        raise
+
+    clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
+                  {'user': USER,
+                   'pass': PASS,
+                   'host': HOST,
+                   'cloned_repo': repo, }
+
+    dest = path + seq
+    if method == 'pull':
+        stdout, stderr = Command(cwd).execute(backend, method, '--cwd', dest, clone_url)
+    else:
+        stdout, stderr = Command(cwd).execute(backend, method, clone_url, dest)
+        print stdout,'sdasdsadsa'
+        if not no_errors:
+            if backend == 'hg':
+                assert """adding file changes""" in stdout, 'no messages about cloning'
+                assert """abort""" not in stderr , 'got error from clone'
+            elif backend == 'git':
+                assert """Cloning into""" in stdout, 'no messages about cloning'
+
+if __name__ == '__main__':
+    try:
+        create_test_user(force=False)
+        seq = None
+        import time
+
+        try:
+            METHOD = sys.argv[3]
+        except IndexError:
+            pass
+
+        try:
+            backend = sys.argv[4]
+        except IndexError:
+            backend = 'hg'
+
+        if METHOD == 'pull':
+            seq = _RandomNameSequence().next()
+            test_clone_with_credentials(repo=sys.argv[1], method='clone',
+                                        seq=seq, backend=backend)
+        s = time.time()
+        for i in range(1, int(sys.argv[2]) + 1):
+            print 'take', i
+            test_clone_with_credentials(repo=sys.argv[1], method=METHOD,
+                                        seq=seq, backend=backend)
+        print 'time taken %.3f' % (time.time() - s)
+    except Exception, e:
+        sys.exit('stop on %s' % e)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/scripts/manual_test_crawler.py	Mon Jul 20 15:07:54 2015 +0200
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.tests.test_crawer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Test for crawling a project for memory usage
+This should be runned just as regular script together
+with a watch script that will show memory usage.
+
+watch -n1 ./kallithea/tests/mem_watch
+
+This file was forked by the Kallithea project in July 2014.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: Apr 21, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+"""
+
+
+import cookielib
+import urllib
+import urllib2
+import time
+import os
+import sys
+from os.path import join as jn
+from os.path import dirname as dn
+
+__here__ = os.path.abspath(__file__)
+__root__ = dn(dn(dn(__here__)))
+sys.path.append(__root__)
+
+from kallithea.lib import vcs
+from kallithea.lib.compat import OrderedSet
+from kallithea.lib.vcs.exceptions import RepositoryError
+
+PASES = 3
+HOST = 'http://127.0.0.1'
+PORT = 5000
+BASE_URI = '%s:%s/' % (HOST, PORT)
+
+if len(sys.argv) == 2:
+    BASE_URI = sys.argv[1]
+
+if not BASE_URI.endswith('/'):
+    BASE_URI += '/'
+
+print 'Crawling @ %s' % BASE_URI
+BASE_URI += '%s'
+PROJECT_PATH = jn('/', 'home', 'username', 'repos')
+PROJECTS = [
+    #'linux-magx-pbranch',
+    'CPython',
+    'kallithea',
+]
+
+
+cj = cookielib.FileCookieJar('/tmp/rc_test_cookie.txt')
+o = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
+o.addheaders = [
+    ('User-agent', 'kallithea-crawler'),
+    ('Accept-Language', 'en - us, en;q = 0.5')
+]
+
+urllib2.install_opener(o)
+
+
+def _get_repo(proj):
+    if isinstance(proj, basestring):
+        repo = vcs.get_repo(jn(PROJECT_PATH, proj))
+        proj = proj
+    else:
+        repo = proj
+        proj = repo.name
+
+    return repo, proj
+
+
+def test_changelog_walk(proj, pages=100):
+    repo, proj = _get_repo(proj)
+
+    total_time = 0
+    for i in range(1, pages):
+
+        page = '/'.join((proj, 'changelog',))
+
+        full_uri = (BASE_URI % page) + '?' + urllib.urlencode({'page': i})
+        s = time.time()
+        f = o.open(full_uri)
+
+        assert f.url == full_uri, 'URL:%s does not match %s' % (f.url, full_uri)
+
+        size = len(f.read())
+        e = time.time() - s
+        total_time += e
+        print 'visited %s size:%s req:%s ms' % (full_uri, size, e)
+
+    print 'total_time', total_time
+    print 'average on req', total_time / float(pages)
+
+
+def test_changeset_walk(proj, limit=None):
+    repo, proj = _get_repo(proj)
+
+    print 'processing', jn(PROJECT_PATH, proj)
+    total_time = 0
+
+    cnt = 0
+    for i in repo:
+        cnt += 1
+        raw_cs = '/'.join((proj, 'changeset', i.raw_id))
+        if limit and limit == cnt:
+            break
+
+        full_uri = (BASE_URI % raw_cs)
+        print '%s visiting %s\%s' % (cnt, full_uri, i)
+        s = time.time()
+        f = o.open(full_uri)
+        size = len(f.read())
+        e = time.time() - s
+        total_time += e
+        print '%s visited %s\%s size:%s req:%s ms' % (cnt, full_uri, i, size, e)
+
+    print 'total_time', total_time
+    print 'average on req', total_time / float(cnt)
+
+
+def test_files_walk(proj, limit=100):
+    repo, proj = _get_repo(proj)
+
+    print 'processing', jn(PROJECT_PATH, proj)
+    total_time = 0
+
+    paths_ = OrderedSet([''])
+    try:
+        tip = repo.get_changeset('tip')
+        for topnode, dirs, files in tip.walk('/'):
+
+            for dir in dirs:
+                paths_.add(dir.path)
+                for f in dir:
+                    paths_.add(f.path)
+
+            for f in files:
+                paths_.add(f.path)
+
+    except RepositoryError, e:
+        pass
+
+    cnt = 0
+    for f in paths_:
+        cnt += 1
+        if limit and limit == cnt:
+            break
+
+        file_path = '/'.join((proj, 'files', 'tip', f))
+        full_uri = (BASE_URI % file_path)
+        print '%s visiting %s' % (cnt, full_uri)
+        s = time.time()
+        f = o.open(full_uri)
+        size = len(f.read())
+        e = time.time() - s
+        total_time += e
+        print '%s visited OK size:%s req:%s ms' % (cnt, size, e)
+
+    print 'total_time', total_time
+    print 'average on req', total_time / float(cnt)
+
+if __name__ == '__main__':
+    for path in PROJECTS:
+        repo = vcs.get_repo(jn(PROJECT_PATH, path))
+        for i in range(PASES):
+            print 'PASS %s/%s' % (i, PASES)
+            test_changelog_walk(repo, pages=80)
+            test_changeset_walk(repo, limit=100)
+            test_files_walk(repo, limit=100)
--- a/kallithea/tests/scripts/test_concurency.py	Mon Jul 20 15:07:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-kallithea.tests.test_hg_operations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Test suite for making push/pull operations
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Dec 30, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-
-"""
-
-import os
-import sys
-import shutil
-import logging
-from os.path import join as jn
-from os.path import dirname as dn
-
-from tempfile import _RandomNameSequence
-from subprocess import Popen, PIPE
-
-from paste.deploy import appconfig
-from sqlalchemy import engine_from_config
-
-from kallithea.lib.utils import add_cache
-from kallithea.model import init_model
-from kallithea.model import meta
-from kallithea.model.db import User, Repository
-from kallithea.lib.auth import get_crypt_password
-
-from kallithea.tests import TESTS_TMP_PATH, HG_REPO
-from kallithea.config.environment import load_environment
-
-rel_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
-conf = appconfig('config:rc.ini', relative_to=rel_path)
-load_environment(conf.global_conf, conf.local_conf)
-
-add_cache(conf)
-
-USER = 'test_admin'
-PASS = 'test12'
-HOST = 'rc.local'
-METHOD = 'pull'
-DEBUG = True
-log = logging.getLogger(__name__)
-
-
-class Command(object):
-
-    def __init__(self, cwd):
-        self.cwd = cwd
-
-    def execute(self, cmd, *args):
-        """Runs command on the system with given ``args``.
-        """
-
-        command = cmd + ' ' + ' '.join(args)
-        log.debug('Executing %s' % command)
-        if DEBUG:
-            print command
-        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
-        stdout, stderr = p.communicate()
-        if DEBUG:
-            print stdout, stderr
-        return stdout, stderr
-
-
-def get_session():
-    engine = engine_from_config(conf, 'sqlalchemy.db1.')
-    init_model(engine)
-    sa = meta.Session
-    return sa
-
-
-def create_test_user(force=True):
-    print 'creating test user'
-    sa = get_session()
-
-    user = sa.query(User).filter(User.username == USER).scalar()
-
-    if force and user is not None:
-        print 'removing current user'
-        for repo in sa.query(Repository).filter(Repository.user == user).all():
-            sa.delete(repo)
-        sa.delete(user)
-        sa.commit()
-
-    if user is None or force:
-        print 'creating new one'
-        new_usr = User()
-        new_usr.username = USER
-        new_usr.password = get_crypt_password(PASS)
-        new_usr.email = 'mail@mail.com'
-        new_usr.name = 'test'
-        new_usr.lastname = 'lasttestname'
-        new_usr.active = True
-        new_usr.admin = True
-        sa.add(new_usr)
-        sa.commit()
-
-    print 'done'
-
-
-def create_test_repo(force=True):
-    print 'creating test repo'
-    from kallithea.model.repo import RepoModel
-    sa = get_session()
-
-    user = sa.query(User).filter(User.username == USER).scalar()
-    if user is None:
-        raise Exception('user not found')
-
-    repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar()
-
-    if repo is None:
-        print 'repo not found creating'
-
-        form_data = {'repo_name': HG_REPO,
-                     'repo_type': 'hg',
-                     'private':False,
-                     'clone_uri': '' }
-        rm = RepoModel(sa)
-        rm.base_path = '/home/hg'
-        rm.create(form_data, user)
-
-    print 'done'
-
-
-def set_anonymous_access(enable=True):
-    sa = get_session()
-    user = sa.query(User).filter(User.username == 'default').one()
-    user.active = enable
-    sa.add(user)
-    sa.commit()
-
-
-def get_anonymous_access():
-    sa = get_session()
-    return sa.query(User).filter(User.username == 'default').one().active
-
-
-#==============================================================================
-# TESTS
-#==============================================================================
-def test_clone_with_credentials(no_errors=False, repo=HG_REPO, method=METHOD,
-                                seq=None, backend='hg'):
-    cwd = path = jn(TESTS_TMP_PATH, repo)
-
-    if seq is None:
-        seq = _RandomNameSequence().next()
-
-    try:
-        shutil.rmtree(path, ignore_errors=True)
-        os.makedirs(path)
-        #print 'made dirs %s' % jn(path)
-    except OSError:
-        raise
-
-    clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
-                  {'user': USER,
-                   'pass': PASS,
-                   'host': HOST,
-                   'cloned_repo': repo, }
-
-    dest = path + seq
-    if method == 'pull':
-        stdout, stderr = Command(cwd).execute(backend, method, '--cwd', dest, clone_url)
-    else:
-        stdout, stderr = Command(cwd).execute(backend, method, clone_url, dest)
-        print stdout,'sdasdsadsa'
-        if not no_errors:
-            if backend == 'hg':
-                assert """adding file changes""" in stdout, 'no messages about cloning'
-                assert """abort""" not in stderr , 'got error from clone'
-            elif backend == 'git':
-                assert """Cloning into""" in stdout, 'no messages about cloning'
-
-if __name__ == '__main__':
-    try:
-        create_test_user(force=False)
-        seq = None
-        import time
-
-        try:
-            METHOD = sys.argv[3]
-        except IndexError:
-            pass
-
-        try:
-            backend = sys.argv[4]
-        except IndexError:
-            backend = 'hg'
-
-        if METHOD == 'pull':
-            seq = _RandomNameSequence().next()
-            test_clone_with_credentials(repo=sys.argv[1], method='clone',
-                                        seq=seq, backend=backend)
-        s = time.time()
-        for i in range(1, int(sys.argv[2]) + 1):
-            print 'take', i
-            test_clone_with_credentials(repo=sys.argv[1], method=METHOD,
-                                        seq=seq, backend=backend)
-        print 'time taken %.3f' % (time.time() - s)
-    except Exception, e:
-        sys.exit('stop on %s' % e)
--- a/kallithea/tests/scripts/test_crawler.py	Mon Jul 20 15:07:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,189 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-kallithea.tests.test_crawer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Test for crawling a project for memory usage
-This should be runned just as regular script together
-with a watch script that will show memory usage.
-
-watch -n1 ./kallithea/tests/mem_watch
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 21, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-
-import cookielib
-import urllib
-import urllib2
-import time
-import os
-import sys
-from os.path import join as jn
-from os.path import dirname as dn
-
-__here__ = os.path.abspath(__file__)
-__root__ = dn(dn(dn(__here__)))
-sys.path.append(__root__)
-
-from kallithea.lib import vcs
-from kallithea.lib.compat import OrderedSet
-from kallithea.lib.vcs.exceptions import RepositoryError
-
-PASES = 3
-HOST = 'http://127.0.0.1'
-PORT = 5000
-BASE_URI = '%s:%s/' % (HOST, PORT)
-
-if len(sys.argv) == 2:
-    BASE_URI = sys.argv[1]
-
-if not BASE_URI.endswith('/'):
-    BASE_URI += '/'
-
-print 'Crawling @ %s' % BASE_URI
-BASE_URI += '%s'
-PROJECT_PATH = jn('/', 'home', 'marcink', 'repos')
-PROJECTS = [
-    #'linux-magx-pbranch',
-    'CPython',
-    'kallithea',
-]
-
-
-cj = cookielib.FileCookieJar('/tmp/rc_test_cookie.txt')
-o = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
-o.addheaders = [
-    ('User-agent', 'kallithea-crawler'),
-    ('Accept-Language', 'en - us, en;q = 0.5')
-]
-
-urllib2.install_opener(o)
-
-
-def _get_repo(proj):
-    if isinstance(proj, basestring):
-        repo = vcs.get_repo(jn(PROJECT_PATH, proj))
-        proj = proj
-    else:
-        repo = proj
-        proj = repo.name
-
-    return repo, proj
-
-
-def test_changelog_walk(proj, pages=100):
-    repo, proj = _get_repo(proj)
-
-    total_time = 0
-    for i in range(1, pages):
-
-        page = '/'.join((proj, 'changelog',))
-
-        full_uri = (BASE_URI % page) + '?' + urllib.urlencode({'page': i})
-        s = time.time()
-        f = o.open(full_uri)
-
-        assert f.url == full_uri, 'URL:%s does not match %s' % (f.url, full_uri)
-
-        size = len(f.read())
-        e = time.time() - s
-        total_time += e
-        print 'visited %s size:%s req:%s ms' % (full_uri, size, e)
-
-    print 'total_time', total_time
-    print 'average on req', total_time / float(pages)
-
-
-def test_changeset_walk(proj, limit=None):
-    repo, proj = _get_repo(proj)
-
-    print 'processing', jn(PROJECT_PATH, proj)
-    total_time = 0
-
-    cnt = 0
-    for i in repo:
-        cnt += 1
-        raw_cs = '/'.join((proj, 'changeset', i.raw_id))
-        if limit and limit == cnt:
-            break
-
-        full_uri = (BASE_URI % raw_cs)
-        print '%s visiting %s\%s' % (cnt, full_uri, i)
-        s = time.time()
-        f = o.open(full_uri)
-        size = len(f.read())
-        e = time.time() - s
-        total_time += e
-        print '%s visited %s\%s size:%s req:%s ms' % (cnt, full_uri, i, size, e)
-
-    print 'total_time', total_time
-    print 'average on req', total_time / float(cnt)
-
-
-def test_files_walk(proj, limit=100):
-    repo, proj = _get_repo(proj)
-
-    print 'processing', jn(PROJECT_PATH, proj)
-    total_time = 0
-
-    paths_ = OrderedSet([''])
-    try:
-        tip = repo.get_changeset('tip')
-        for topnode, dirs, files in tip.walk('/'):
-
-            for dir in dirs:
-                paths_.add(dir.path)
-                for f in dir:
-                    paths_.add(f.path)
-
-            for f in files:
-                paths_.add(f.path)
-
-    except RepositoryError, e:
-        pass
-
-    cnt = 0
-    for f in paths_:
-        cnt += 1
-        if limit and limit == cnt:
-            break
-
-        file_path = '/'.join((proj, 'files', 'tip', f))
-        full_uri = (BASE_URI % file_path)
-        print '%s visiting %s' % (cnt, full_uri)
-        s = time.time()
-        f = o.open(full_uri)
-        size = len(f.read())
-        e = time.time() - s
-        total_time += e
-        print '%s visited OK size:%s req:%s ms' % (cnt, size, e)
-
-    print 'total_time', total_time
-    print 'average on req', total_time / float(cnt)
-
-if __name__ == '__main__':
-    for path in PROJECTS:
-        repo = vcs.get_repo(jn(PROJECT_PATH, path))
-        for i in range(PASES):
-            print 'PASS %s/%s' % (i, PASES)
-            test_changelog_walk(repo, pages=80)
-            test_changeset_walk(repo, limit=100)
-            test_files_walk(repo, limit=100)
--- a/kallithea/tests/vcs/base.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/vcs/base.py	Mon Jul 20 15:07:54 2015 +0200
@@ -13,7 +13,7 @@
 from kallithea.lib.vcs.nodes import FileNode
 
 
-class BackendTestMixin(object):
+class _BackendTestMixin(object):
     """
     This is a backend independent test case class which should be created
     with ``type`` method.
@@ -103,7 +103,7 @@
         'backend_alias': alias,
     }
     cls_name = ''.join(('%s base backend test' % alias).title().split())
-    bases = (BackendTestMixin, unittest.TestCase)
+    bases = (_BackendTestMixin, unittest.TestCase)
     globals()[cls_name] = type(cls_name, bases, attrs)
 
 
--- a/kallithea/tests/vcs/test_archives.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/vcs/test_archives.py	Mon Jul 20 15:07:54 2015 +0200
@@ -6,14 +6,14 @@
 import datetime
 import tempfile
 import StringIO
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 from kallithea.lib.vcs.exceptions import VCSError
 from kallithea.lib.vcs.nodes import FileNode
 from kallithea.lib.vcs.utils.compat import unittest
 
 
-class ArchivesTestCaseMixin(BackendTestMixin):
+class ArchivesTestCaseMixin(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/kallithea/tests/vcs/test_branches.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/vcs/test_branches.py	Mon Jul 20 15:07:54 2015 +0200
@@ -5,11 +5,11 @@
 from kallithea.lib.vcs.utils.compat import unittest
 from kallithea.lib.vcs.nodes import FileNode
 
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 
 
-class BranchesTestCaseMixin(BackendTestMixin):
+class BranchesTestCaseMixin(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/kallithea/tests/vcs/test_changesets.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/vcs/test_changesets.py	Mon Jul 20 15:07:54 2015 +0200
@@ -4,7 +4,7 @@
 import time
 import datetime
 from kallithea.lib import vcs
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 
 from kallithea.lib.vcs.backends.base import BaseChangeset
@@ -50,7 +50,7 @@
             'removed': [],
         })
 
-class ChangesetsWithCommitsTestCaseixin(BackendTestMixin):
+class _ChangesetsWithCommitsTestCaseixin(_BackendTestMixin):
     recreate_repo_per_test = True
 
     @classmethod
@@ -146,7 +146,7 @@
             self.assertEqual([sha], self.repo.get_changeset(test_rev).children)
 
 
-class ChangesetsTestCaseMixin(BackendTestMixin):
+class _ChangesetsTestCaseMixin(_BackendTestMixin):
     recreate_repo_per_test = False
 
     @classmethod
@@ -301,7 +301,7 @@
             list(self.repo.get_changesets(start=last-1, end=0))
 
 
-class ChangesetsChangesTestCaseMixin(BackendTestMixin):
+class _ChangesetsChangesTestCaseMixin(_BackendTestMixin):
     recreate_repo_per_test = False
 
     @classmethod
@@ -373,17 +373,17 @@
     }
     # tests with additional commits
     cls_name = ''.join(('%s changesets with commits test' % alias).title().split())
-    bases = (ChangesetsWithCommitsTestCaseixin, unittest.TestCase)
+    bases = (_ChangesetsWithCommitsTestCaseixin, unittest.TestCase)
     globals()[cls_name] = type(cls_name, bases, attrs)
 
     # tests without additional commits
     cls_name = ''.join(('%s changesets test' % alias).title().split())
-    bases = (ChangesetsTestCaseMixin, unittest.TestCase)
+    bases = (_ChangesetsTestCaseMixin, unittest.TestCase)
     globals()[cls_name] = type(cls_name, bases, attrs)
 
     # tests changes
     cls_name = ''.join(('%s changesets changes test' % alias).title().split())
-    bases = (ChangesetsChangesTestCaseMixin, unittest.TestCase)
+    bases = (_ChangesetsChangesTestCaseMixin, unittest.TestCase)
     globals()[cls_name] = type(cls_name, bases, attrs)
 
 
--- a/kallithea/tests/vcs/test_getitem.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/vcs/test_getitem.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,13 +1,13 @@
 from __future__ import with_statement
 
 import datetime
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 from kallithea.lib.vcs.nodes import FileNode
 from kallithea.lib.vcs.utils.compat import unittest
 
 
-class GetitemTestCaseMixin(BackendTestMixin):
+class GetitemTestCaseMixin(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/kallithea/tests/vcs/test_getslice.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/vcs/test_getslice.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,13 +1,13 @@
 from __future__ import with_statement
 
 import datetime
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 from kallithea.lib.vcs.nodes import FileNode
 from kallithea.lib.vcs.utils.compat import unittest
 
 
-class GetsliceTestCaseMixin(BackendTestMixin):
+class GetsliceTestCaseMixin(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/kallithea/tests/vcs/test_git.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/vcs/test_git.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,6 +1,7 @@
 from __future__ import with_statement
 
 import os
+import sys
 import mock
 import datetime
 import urllib2
@@ -8,7 +9,7 @@
 from kallithea.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError
 from kallithea.lib.vcs.nodes import NodeKind, FileNode, DirNode, NodeState
 from kallithea.lib.vcs.utils.compat import unittest
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir
 
 
@@ -39,12 +40,20 @@
             clone_fail_repo.clone(repo_inject_path, update_after_clone=True,)
 
         # Verify correct quoting of evil characters that should work on posix file systems
-        tricky_path = get_new_dir("tricky-path-repo-$'\"`")
+        if sys.platform == 'win32':
+            # windows does not allow '"' in dir names
+            tricky_path = get_new_dir("tricky-path-repo-$'`")
+        else:
+            tricky_path = get_new_dir("tricky-path-repo-$'\"`")
         successfully_cloned = GitRepository(tricky_path, src_url=TEST_GIT_REPO, update_after_clone=True, create=True)
         # Repo should have been created
         self.assertFalse(successfully_cloned._repo.bare)
 
-        tricky_path_2 = get_new_dir("tricky-path-2-repo-$'\"`")
+        if sys.platform == 'win32':
+            # windows does not allow '"' in dir names
+            tricky_path_2 = get_new_dir("tricky-path-2-repo-$'`")
+        else:
+            tricky_path_2 = get_new_dir("tricky-path-2-repo-$'\"`")
         successfully_cloned2 = GitRepository(tricky_path_2, src_url=tricky_path, bare=True, create=True)
         # Repo should have been created and thus used correct quoting for clone
         self.assertTrue(successfully_cloned2._repo.bare)
@@ -53,6 +62,12 @@
         successfully_cloned.pull(tricky_path_2)
         successfully_cloned2.fetch(tricky_path)
 
+    def test_repo_create_with_spaces_in_path(self):
+        repo_path = get_new_dir("path with spaces")
+        repo = GitRepository(repo_path, src_url=None, bare=True, create=True)
+        # Repo should have been created
+        self.assertTrue(repo._repo.bare)
+
     def test_repo_clone(self):
         self.__check_for_existing_repo()
         repo = GitRepository(TEST_GIT_REPO)
@@ -64,6 +79,15 @@
             raw_id = changeset.raw_id
             self.assertEqual(raw_id, repo_clone.get_changeset(raw_id).raw_id)
 
+    def test_repo_clone_with_spaces_in_path(self):
+        repo_path = get_new_dir("path with spaces")
+        successfully_cloned = GitRepository(repo_path, src_url=TEST_GIT_REPO, update_after_clone=True, create=True)
+        # Repo should have been created
+        self.assertFalse(successfully_cloned._repo.bare)
+
+        successfully_cloned.pull(TEST_GIT_REPO)
+        self.repo.fetch(repo_path)
+
     def test_repo_clone_without_create(self):
         self.assertRaises(RepositoryError, GitRepository,
             TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO)
@@ -620,7 +644,7 @@
             changeset.added
 
 
-class GitSpecificWithRepoTest(BackendTestMixin, unittest.TestCase):
+class GitSpecificWithRepoTest(_BackendTestMixin, unittest.TestCase):
     backend_alias = 'git'
 
     @classmethod
@@ -658,37 +682,37 @@
             'base')
 
     def test_workdir_get_branch(self):
-        self.repo.run_git_command('checkout -b production')
+        self.repo.run_git_command(['checkout', '-b', 'production'])
         # Regression test: one of following would fail if we don't check
         # .git/HEAD file
-        self.repo.run_git_command('checkout production')
+        self.repo.run_git_command(['checkout', 'production'])
         self.assertEqual(self.repo.workdir.get_branch(), 'production')
-        self.repo.run_git_command('checkout master')
+        self.repo.run_git_command(['checkout', 'master'])
         self.assertEqual(self.repo.workdir.get_branch(), 'master')
 
     def test_get_diff_runs_git_command_with_hashes(self):
         self.repo.run_git_command = mock.Mock(return_value=['', ''])
         self.repo.get_diff(0, 1)
         self.repo.run_git_command.assert_called_once_with(
-          'diff -U%s --full-index --binary -p -M --abbrev=40 %s %s' %
-            (3, self.repo._get_revision(0), self.repo._get_revision(1)))
+            ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
+             self.repo._get_revision(0), self.repo._get_revision(1)])
 
     def test_get_diff_runs_git_command_with_str_hashes(self):
         self.repo.run_git_command = mock.Mock(return_value=['', ''])
         self.repo.get_diff(self.repo.EMPTY_CHANGESET, 1)
         self.repo.run_git_command.assert_called_once_with(
-            'show -U%s --full-index --binary -p -M --abbrev=40 %s' %
-            (3, self.repo._get_revision(1)))
+            ['show', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
+             self.repo._get_revision(1)])
 
     def test_get_diff_runs_git_command_with_path_if_its_given(self):
         self.repo.run_git_command = mock.Mock(return_value=['', ''])
         self.repo.get_diff(0, 1, 'foo')
         self.repo.run_git_command.assert_called_once_with(
-          'diff -U%s --full-index --binary -p -M --abbrev=40 %s %s -- "foo"'
-            % (3, self.repo._get_revision(0), self.repo._get_revision(1)))
+            ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
+             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
 
 
-class GitRegressionTest(BackendTestMixin, unittest.TestCase):
+class GitRegressionTest(_BackendTestMixin, unittest.TestCase):
     backend_alias = 'git'
 
     @classmethod
--- a/kallithea/tests/vcs/test_repository.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/vcs/test_repository.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,6 +1,6 @@
 from __future__ import with_statement
 import datetime
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 from kallithea.tests.vcs.conf import TEST_USER_CONFIG_FILE
 from kallithea.lib.vcs.nodes import FileNode
@@ -8,7 +8,7 @@
 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
 
 
-class RepositoryBaseTest(BackendTestMixin):
+class RepositoryBaseTest(_BackendTestMixin):
     recreate_repo_per_test = False
 
     @classmethod
@@ -46,7 +46,7 @@
         self.assertTrue(self.repo != dummy())
 
 
-class RepositoryGetDiffTest(BackendTestMixin):
+class RepositoryGetDiffTest(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/kallithea/tests/vcs/test_tags.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/vcs/test_tags.py	Mon Jul 20 15:07:54 2015 +0200
@@ -1,13 +1,13 @@
 from __future__ import with_statement
 
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 from kallithea.lib.vcs.exceptions import TagAlreadyExistError
 from kallithea.lib.vcs.exceptions import TagDoesNotExistError
 from kallithea.lib.vcs.utils.compat import unittest
 
 
-class TagsTestCaseMixin(BackendTestMixin):
+class TagsTestCaseMixin(_BackendTestMixin):
 
     def test_new_tag(self):
         tip = self.repo.get_changeset()
--- a/kallithea/tests/vcs/test_utils.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/vcs/test_utils.py	Mon Jul 20 15:07:54 2015 +0200
@@ -185,14 +185,14 @@
 
 
 class TestAuthorExtractors(unittest.TestCase):
-    TEST_AUTHORS = [('Marcin Kuzminski <marcin@python-works.com>',
-                    ('Marcin Kuzminski', 'marcin@python-works.com')),
-                  ('Marcin Kuzminski Spaces < marcin@python-works.com >',
-                    ('Marcin Kuzminski Spaces', 'marcin@python-works.com')),
-                  ('Marcin Kuzminski <marcin.kuzminski@python-works.com>',
-                    ('Marcin Kuzminski', 'marcin.kuzminski@python-works.com')),
-                  ('mrf RFC_SPEC <marcin+kuzminski@python-works.com>',
-                    ('mrf RFC_SPEC', 'marcin+kuzminski@python-works.com')),
+    TEST_AUTHORS = [("Username Last'o'Name <username@python-works.com>",
+                    ("Username Last'o'Name", "username@python-works.com")),
+                  ("Username Last'o'Name Spaces < username@python-works.com >",
+                    ("Username Last'o'Name Spaces", "username@python-works.com")),
+                  ("Username Last'o'Name <username.lastname@python-works.com>",
+                    ("Username Last'o'Name", "username.lastname@python-works.com")),
+                  ('mrf RFC_SPEC <username+lastname@python-works.com>',
+                    ('mrf RFC_SPEC', 'username+lastname@python-works.com')),
                   ('username <user@email.com>',
                     ('username', 'user@email.com')),
                   ('username <user@email.com',
--- a/kallithea/tests/vcs/test_workdirs.py	Mon Jul 20 15:07:23 2015 +0200
+++ b/kallithea/tests/vcs/test_workdirs.py	Mon Jul 20 15:07:54 2015 +0200
@@ -3,11 +3,11 @@
 import datetime
 from kallithea.lib.vcs.nodes import FileNode
 from kallithea.lib.vcs.utils.compat import unittest
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 
 
-class WorkdirTestCaseMixin(BackendTestMixin):
+class WorkdirTestCaseMixin(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/production.ini	Mon Jul 20 15:07:23 2015 +0200
+++ b/production.ini	Mon Jul 20 15:07:54 2015 +0200
@@ -133,7 +133,7 @@
 host = 127.0.0.1
 port = 5000
 
-## prefix middleware for rc
+## middleware for hosting the WSGI application under a URL prefix
 #[filter:proxy-prefix]
 #use = egg:PasteDeploy#prefix
 #prefix = /<your-prefix>
--- a/setup.cfg	Mon Jul 20 15:07:23 2015 +0200
+++ b/setup.cfg	Mon Jul 20 15:07:54 2015 +0200
@@ -10,6 +10,16 @@
 detailed-errors = 1
 nologcapture = 1
 
+[pytest]
+# only look for tests in kallithea/tests
+python_files = kallithea/tests/**/test_*.py
+addopts =
+    # --verbose
+    # show extra test summary info as specified by chars (f)ailed, (E)error, (s)skipped, (x)failed, (X)passed, (w)warnings.
+    -rfEsxXw
+    # Shorter scrollbacks; less stuff to scroll through
+    --tb=short
+
 [compile_catalog]
 domain = kallithea
 directory = kallithea/i18n
--- a/test.ini	Mon Jul 20 15:07:23 2015 +0200
+++ b/test.ini	Mon Jul 20 15:07:54 2015 +0200
@@ -133,7 +133,7 @@
 host = 127.0.0.1
 port = 5000
 
-## prefix middleware for rc
+## middleware for hosting the WSGI application under a URL prefix
 #[filter:proxy-prefix]
 #use = egg:PasteDeploy#prefix
 #prefix = /<your-prefix>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tox.ini	Mon Jul 20 15:07:54 2015 +0200
@@ -0,0 +1,12 @@
+[tox]
+envlist = py{26,27}-{pytest,nose}
+
+[testenv]
+setenv =
+    PYTHONHASHSEED = 0
+deps =
+    nose: nose
+    pytest: pytest
+commands =
+    nose: nosetests {posargs}
+    pytest: py.test {posargs}